2018-05-31 20:26:44 -04:00
#!/usr/bin/env python3
2019-12-25 13:35:31 -05:00
# pvc.py - PVC client command-line interface
2018-06-06 01:47:53 -04:00
# Part of the Parallel Virtual Cluster (PVC) system
#
2020-01-08 19:38:02 -05:00
# Copyright (C) 2018-2020 Joshua M. Boniface <joshua@boniface.me>
2018-06-06 01:47:53 -04:00
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
###############################################################################
2018-06-14 12:07:46 -04:00
import socket
import click
2018-09-25 02:32:08 -04:00
import tempfile
import os
2020-01-08 18:36:31 -05:00
import stat
2018-09-25 02:32:08 -04:00
import subprocess
import difflib
import re
2020-01-08 14:41:19 -05:00
import time
2018-09-25 02:32:08 -04:00
import colorama
2019-03-12 22:55:29 -04:00
import yaml
2019-12-30 13:27:40 -05:00
import json
2019-12-27 09:52:16 -05:00
import lxml . etree as etree
2019-12-25 13:35:31 -05:00
2019-12-30 13:27:40 -05:00
from distutils . util import strtobool
2019-12-25 14:10:23 -05:00
import cli_lib . ansiprint as ansiprint
import cli_lib . cluster as pvc_cluster
import cli_lib . node as pvc_node
import cli_lib . vm as pvc_vm
import cli_lib . network as pvc_network
import cli_lib . ceph as pvc_ceph
2020-01-02 11:18:46 -05:00
import cli_lib . provisioner as pvc_provisioner
2018-06-05 01:39:59 -04:00
2019-05-23 22:27:34 -04:00
myhostname = socket . gethostname ( ) . split ( ' . ' ) [ 0 ]
2018-06-14 11:57:36 -04:00
zk_host = ' '
2019-12-30 13:27:40 -05:00
default_store_data = {
2020-02-08 19:16:19 -05:00
' cfgfile ' : ' /etc/pvc/pvcapid.yaml ' # pvc/api/listen_address, pvc/api/listen_port
2019-12-30 13:27:40 -05:00
}
#
# Data store handling functions
#
def read_from_yaml ( cfgfile ) :
with open ( cfgfile , ' r ' ) as fh :
api_config = yaml . load ( fh , Loader = yaml . BaseLoader )
host = api_config [ ' pvc ' ] [ ' api ' ] [ ' listen_address ' ]
port = api_config [ ' pvc ' ] [ ' api ' ] [ ' listen_port ' ]
if strtobool ( api_config [ ' pvc ' ] [ ' api ' ] [ ' ssl ' ] [ ' enabled ' ] ) :
scheme = ' https '
else :
scheme = ' http '
2020-01-08 18:36:31 -05:00
if strtobool ( api_config [ ' pvc ' ] [ ' api ' ] [ ' authentication ' ] [ ' enabled ' ] ) :
# Always use the first token
api_key = api_config [ ' pvc ' ] [ ' api ' ] [ ' authentication ' ] [ ' tokens ' ] [ 0 ] [ ' token ' ]
else :
api_key = ' N/A '
return host , port , scheme , api_key
2019-12-30 13:29:07 -05:00
2019-12-30 13:27:40 -05:00
def get_config ( store_data , cluster = None ) :
# This is generally static
prefix = ' /api/v1 '
cluster_details = store_data . get ( cluster )
if not cluster_details :
cluster_details = default_store_data
cluster = ' local '
if cluster_details . get ( ' cfgfile ' , None ) :
# This is a reference to an API configuration; grab the details from its listen address
cfgfile = cluster_details . get ( ' cfgfile ' )
if os . path . isfile ( cfgfile ) :
2020-01-08 18:36:31 -05:00
host , port , scheme , api_key = read_from_yaml ( cfgfile )
2019-12-30 13:27:40 -05:00
else :
2020-01-02 11:19:11 -05:00
return { ' badcfg ' : True }
2020-02-16 20:01:24 -05:00
# Handle an all-wildcard address
if host == ' 0.0.0.0 ' :
host = ' 127.0.0.1 '
2019-12-30 13:27:40 -05:00
else :
# This is a static configuration, get the raw details
host = cluster_details [ ' host ' ]
port = cluster_details [ ' port ' ]
scheme = cluster_details [ ' scheme ' ]
2020-01-08 18:36:31 -05:00
api_key = cluster_details [ ' api_key ' ]
2019-12-30 13:27:40 -05:00
config = dict ( )
config [ ' debug ' ] = False
config [ ' cluster ' ] = cluster
config [ ' api_host ' ] = ' {} : {} ' . format ( host , port )
config [ ' api_scheme ' ] = scheme
2020-01-08 18:36:31 -05:00
config [ ' api_key ' ] = api_key
2019-12-30 13:27:40 -05:00
config [ ' api_prefix ' ] = prefix
return config
def get_store ( store_path ) :
store_file = ' {} /pvc-cli.json ' . format ( store_path )
with open ( store_file , ' r ' ) as fh :
store_data = json . loads ( fh . read ( ) )
return store_data
def update_store ( store_path , store_data ) :
store_file = ' {} /pvc-cli.json ' . format ( store_path )
with open ( store_file , ' w ' ) as fh :
fh . write ( json . dumps ( store_data , sort_keys = True , indent = 4 ) )
2020-01-08 18:36:31 -05:00
# Ensure file has 0600 permissions due to API key storage
os . chmod ( store_file , 0o600 )
2019-12-30 13:27:40 -05:00
home_dir = os . environ . get ( ' HOME ' , None )
if home_dir :
store_path = ' {} /.config/pvc ' . format ( home_dir )
else :
print ( ' No home dir found - not permanently saving any configurations as this user ' )
store_path = ' /tmp/pvc '
if not os . path . isdir ( store_path ) :
os . makedirs ( store_path )
if not os . path . isfile ( store_path + ' /pvc-cli.json ' ) :
update_store ( store_path , { " local " : default_store_data } )
2019-12-25 19:52:15 -05:00
2018-06-11 01:35:50 -04:00
CONTEXT_SETTINGS = dict ( help_option_names = [ ' -h ' , ' --help ' ] , max_content_width = 120 )
2019-12-25 19:52:15 -05:00
def cleanup ( retcode , retmsg ) :
2018-09-20 03:25:58 -04:00
if retcode == True :
if retmsg != ' ' :
click . echo ( retmsg )
exit ( 0 )
else :
if retmsg != ' ' :
click . echo ( retmsg )
exit ( 1 )
2019-12-30 13:27:40 -05:00
###############################################################################
# pvc cluster
###############################################################################
@click.group ( name = ' cluster ' , short_help = ' Manage PVC cluster connections. ' , context_settings = CONTEXT_SETTINGS )
def cli_cluster ( ) :
"""
Manage the PVC clusters this CLI can connect to .
"""
pass
###############################################################################
# pvc cluster add
###############################################################################
@click.command ( name = ' add ' , short_help = ' Add a new cluster to the client. ' )
@click.option (
' -a ' , ' --address ' , ' address ' , required = True ,
help = ' The IP address or hostname of the cluster API client. '
)
@click.option (
' -p ' , ' --port ' , ' port ' , required = False , default = 7370 , show_default = True ,
help = ' The cluster API client port. '
)
@click.option (
' -s/-S ' , ' --ssl/--no-ssl ' , ' ssl ' , is_flag = True , default = False , show_default = True ,
help = ' Whether to use SSL or not. '
)
2020-01-08 18:36:31 -05:00
@click.option (
' -k ' , ' --api-key ' , ' api_key ' , required = False , default = None ,
help = ' An API key to authenticate against the cluster. '
)
2019-12-30 13:27:40 -05:00
@click.argument (
' name '
)
2020-01-08 18:36:31 -05:00
def cluster_add ( address , port , ssl , name , api_key ) :
2019-12-30 13:27:40 -05:00
"""
Add a new PVC cluster NAME , via its API connection details , to the configuration of the local CLI client . Replaces any existing cluster with this name .
"""
if ssl :
scheme = ' https '
else :
scheme = ' http '
# Get the existing data
existing_config = get_store ( store_path )
# Append our new entry to the end
existing_config [ name ] = {
' host ' : address ,
' port ' : port ,
2020-01-08 18:36:31 -05:00
' scheme ' : scheme ,
' api_key ' : api_key
2019-12-30 13:27:40 -05:00
}
# Update the store
update_store ( store_path , existing_config )
2020-01-06 09:18:22 -05:00
click . echo ( ' Added new cluster " {} " at host " {} " to local database ' . format ( name , address ) )
2019-12-30 13:27:40 -05:00
###############################################################################
# pvc cluster remove
###############################################################################
@click.command ( name = ' remove ' , short_help = ' Remove a cluster from the client. ' )
@click.argument (
' name '
)
def cluster_remove ( name ) :
"""
Remove a PVC cluster from the configuration of the local CLI client .
"""
# Get the existing data
existing_config = get_store ( store_path )
# Remove the entry matching the name
try :
existing_config . pop ( name )
except KeyError :
print ( ' No cluster with name " {} " found ' . format ( name ) )
# Update the store
update_store ( store_path , existing_config )
2020-01-06 09:18:22 -05:00
click . echo ( ' Removed cluster " {} " from local database ' . format ( name ) )
2019-12-30 13:27:40 -05:00
###############################################################################
# pvc cluster list
###############################################################################
@click.command ( name = ' list ' , short_help = ' List all available clusters. ' )
def cluster_list ( ) :
"""
List all the available PVC clusters configured in this CLI instance .
"""
# Get the existing data
clusters = get_store ( store_path )
# Find the lengths of each column
name_length = 5
address_length = 10
port_length = 5
2020-01-08 18:36:31 -05:00
scheme_length = 7
api_key_length = 8
2019-12-30 13:27:40 -05:00
for cluster in clusters :
cluster_details = clusters [ cluster ]
if cluster_details . get ( ' cfgfile ' , None ) :
# This is a reference to an API configuration; grab the details from its listen address
cfgfile = cluster_details . get ( ' cfgfile ' )
if os . path . isfile ( cfgfile ) :
2020-01-08 18:36:31 -05:00
address , port , scheme , api_key = read_from_yaml ( cfgfile )
2020-01-02 12:18:41 -05:00
else :
2020-01-08 18:36:31 -05:00
address , port , scheme , api_key = ' N/A ' , ' N/A ' , ' N/A ' , ' N/A '
2019-12-30 13:27:40 -05:00
else :
2020-01-06 09:18:22 -05:00
address = cluster_details . get ( ' host ' , ' N/A ' )
port = cluster_details . get ( ' port ' , ' N/A ' )
scheme = cluster_details . get ( ' scheme ' , ' N/A ' )
2020-01-08 18:36:31 -05:00
api_key = cluster_details . get ( ' api_key ' , ' N/A ' )
if not api_key :
api_key = ' N/A '
2019-12-30 13:27:40 -05:00
_name_length = len ( cluster ) + 1
if _name_length > name_length :
name_length = _name_length
_address_length = len ( address ) + 1
if _address_length > address_length :
address_length = _address_length
_port_length = len ( str ( port ) ) + 1
if _port_length > port_length :
port_length = _port_length
_scheme_length = len ( scheme ) + 1
if _scheme_length > scheme_length :
scheme_length = _scheme_length
2020-01-08 18:36:31 -05:00
_api_key_length = len ( api_key ) + 1
if _api_key_length > api_key_length :
api_key_length = _api_key_length
2019-12-30 13:27:40 -05:00
# Display the data nicely
click . echo ( " Available clusters: " )
click . echo ( )
click . echo (
2020-01-08 18:36:31 -05:00
' {bold} { name: < {name_length} } { address: < {address_length} } { port: < {port_length} } { scheme: < {scheme_length} } { api_key: < {api_key_length} } {end_bold} ' . format (
2019-12-30 13:27:40 -05:00
bold = ansiprint . bold ( ) ,
end_bold = ansiprint . end ( ) ,
name = " Name " ,
name_length = name_length ,
address = " Address " ,
address_length = address_length ,
port = " Port " ,
port_length = port_length ,
scheme = " Scheme " ,
2020-01-08 18:36:31 -05:00
scheme_length = scheme_length ,
api_key = " API Key " ,
api_key_length = api_key_length
2019-12-30 13:27:40 -05:00
)
)
for cluster in clusters :
cluster_details = clusters [ cluster ]
if cluster_details . get ( ' cfgfile ' , None ) :
# This is a reference to an API configuration; grab the details from its listen address
if os . path . isfile ( cfgfile ) :
2020-01-08 18:36:31 -05:00
address , port , scheme , api_key = read_from_yaml ( cfgfile )
2020-01-06 09:18:22 -05:00
else :
address = ' N/A '
port = ' N/A '
scheme = ' N/A '
2020-01-08 18:36:31 -05:00
api_key = ' N/A '
2019-12-30 13:27:40 -05:00
else :
2020-01-06 09:18:22 -05:00
address = cluster_details . get ( ' host ' , ' N/A ' )
port = cluster_details . get ( ' port ' , ' N/A ' )
scheme = cluster_details . get ( ' scheme ' , ' N/A ' )
2020-01-08 18:36:31 -05:00
api_key = cluster_details . get ( ' api_key ' , ' N/A ' )
if not api_key :
api_key = ' N/A '
2019-12-30 13:27:40 -05:00
click . echo (
2020-01-08 18:36:31 -05:00
' {bold} { name: < {name_length} } { address: < {address_length} } { port: < {port_length} } { scheme: < {scheme_length} } { api_key: < {api_key_length} } {end_bold} ' . format (
2019-12-30 14:19:17 -05:00
bold = ' ' ,
end_bold = ' ' ,
2019-12-30 13:27:40 -05:00
name = cluster ,
name_length = name_length ,
address = address ,
address_length = address_length ,
port = port ,
port_length = port_length ,
scheme = scheme ,
2020-01-08 18:36:31 -05:00
scheme_length = scheme_length ,
api_key = api_key ,
api_key_length = api_key_length
2019-12-30 13:27:40 -05:00
)
)
2019-12-30 13:29:07 -05:00
2019-12-30 13:27:40 -05:00
2018-06-05 01:39:59 -04:00
###############################################################################
# pvc node
###############################################################################
2018-10-14 02:01:35 -04:00
@click.group ( name = ' node ' , short_help = ' Manage a PVC node. ' , context_settings = CONTEXT_SETTINGS )
2018-09-20 03:25:58 -04:00
def cli_node ( ) :
2018-06-05 01:39:59 -04:00
"""
Manage the state of a node in the PVC cluster .
"""
2020-01-02 11:19:11 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 11:19:11 -05:00
exit ( 1 )
2018-06-05 01:39:59 -04:00
2018-10-14 02:01:35 -04:00
###############################################################################
# pvc node secondary
###############################################################################
@click.command ( name = ' secondary ' , short_help = ' Set a node in secondary node status. ' )
@click.argument (
' node '
)
2020-02-19 14:33:31 -05:00
@click.option (
' -w ' , ' --wait ' , ' wait ' , is_flag = True , default = False ,
help = ' Wait for transition to complete before returning. '
)
def node_secondary ( node , wait ) :
2018-10-14 02:01:35 -04:00
"""
Take NODE out of primary router mode .
"""
2019-12-30 13:29:07 -05:00
2020-01-12 16:00:49 -05:00
task_retcode , task_retdata = pvc_provisioner . task_status ( config , None )
if len ( task_retdata ) > 0 :
2020-01-12 16:06:18 -05:00
click . echo ( " Note: There are currently {} active or queued provisioner jobs on the current primary node. " . format ( len ( task_retdata ) ) )
click . echo ( " These jobs will continue executing, but status will not be visible until the current " )
click . echo ( " node returns to primary state. " )
2020-01-12 16:00:49 -05:00
click . echo ( )
2020-02-19 10:50:21 -05:00
retcode , retmsg = pvc_node . node_coordinator_state ( config , node , ' secondary ' )
2020-02-19 14:33:31 -05:00
if not retcode :
cleanup ( retcode , retmsg )
else :
click . echo ( retmsg )
if wait :
click . echo ( " Waiting for state transition... " , nl = False )
# Every half-second, check if the API is reachable and the node is in secondary state
while True :
try :
_retcode , _retmsg = pvc_node . node_info ( config , node )
if _retmsg [ ' coordinator_state ' ] == ' secondary ' :
retmsg = " done. "
break
else :
time . sleep ( 0.5 )
except :
time . sleep ( 0.5 )
2019-12-25 20:18:53 -05:00
cleanup ( retcode , retmsg )
2018-10-14 02:01:35 -04:00
###############################################################################
# pvc node primary
###############################################################################
@click.command ( name = ' primary ' , short_help = ' Set a node in primary status. ' )
@click.argument (
' node '
)
2020-02-19 14:33:31 -05:00
@click.option (
' -w ' , ' --wait ' , ' wait ' , is_flag = True , default = False ,
help = ' Wait for transition to complete before returning. '
)
def node_primary ( node , wait ) :
2018-10-14 02:01:35 -04:00
"""
Put NODE into primary router mode .
"""
2020-01-12 16:00:49 -05:00
task_retcode , task_retdata = pvc_provisioner . task_status ( config , None )
if len ( task_retdata ) > 0 :
2020-01-12 16:06:18 -05:00
click . echo ( " Note: There are currently {} active or queued provisioner jobs on the current primary node. " . format ( len ( task_retdata ) ) )
click . echo ( " These jobs will continue executing, but status will not be visible until the current " )
click . echo ( " node returns to primary state. " )
2020-01-12 16:00:49 -05:00
click . echo ( )
2020-02-19 10:50:21 -05:00
retcode , retmsg = pvc_node . node_coordinator_state ( config , node , ' primary ' )
2020-02-19 14:33:31 -05:00
if not retcode :
cleanup ( retcode , retmsg )
else :
click . echo ( retmsg )
if wait :
click . echo ( " Waiting for state transition... " , nl = False )
# Every half-second, check if the API is reachable and the node is in secondary state
while True :
try :
_retcode , _retmsg = pvc_node . node_info ( config , node )
if _retmsg [ ' coordinator_state ' ] == ' primary ' :
retmsg = " done. "
break
else :
time . sleep ( 0.5 )
except :
time . sleep ( 0.5 )
2019-12-25 20:18:53 -05:00
cleanup ( retcode , retmsg )
2018-06-05 01:39:59 -04:00
###############################################################################
# pvc node flush
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' flush ' , short_help = ' Take a node out of service. ' )
2018-07-17 01:48:15 -04:00
@click.option (
' -w ' , ' --wait ' , ' wait ' , is_flag = True , default = False ,
help = ' Wait for migrations to complete before returning. '
)
2018-06-16 22:22:07 -04:00
@click.argument (
' node ' , default = myhostname
2018-06-06 20:49:07 -04:00
)
2018-09-20 03:25:58 -04:00
def node_flush ( node , wait ) :
2018-06-05 01:39:59 -04:00
"""
2018-06-16 22:22:07 -04:00
Take NODE out of active service and migrate away all VMs . If unspecified , defaults to this host .
2018-06-05 01:39:59 -04:00
"""
2019-12-30 13:29:07 -05:00
2019-12-25 20:18:53 -05:00
retcode , retmsg = pvc_node . node_domain_state ( config , node , ' flush ' , wait )
cleanup ( retcode , retmsg )
2018-06-05 01:39:59 -04:00
###############################################################################
2018-06-26 23:46:03 -04:00
# pvc node ready/unflush
2018-06-05 01:39:59 -04:00
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' ready ' , short_help = ' Restore node to service. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' node ' , default = myhostname
2018-06-06 20:49:07 -04:00
)
2019-05-11 00:16:38 -04:00
@click.option (
' -w ' , ' --wait ' , ' wait ' , is_flag = True , default = False ,
help = ' Wait for migrations to complete before returning. '
)
def node_ready ( node , wait ) :
2018-07-18 22:58:41 -04:00
"""
Restore NODE to active service and migrate back all VMs . If unspecified , defaults to this host .
"""
2019-12-25 20:18:53 -05:00
retcode , retmsg = pvc_node . node_domain_state ( config , node , ' ready ' , wait )
cleanup ( retcode , retmsg )
2018-06-26 23:46:03 -04:00
2018-07-18 22:28:49 -04:00
@click.command ( name = ' unflush ' , short_help = ' Restore node to service. ' )
2018-06-26 23:46:03 -04:00
@click.argument (
' node ' , default = myhostname
)
2019-05-11 00:16:38 -04:00
@click.option (
' -w ' , ' --wait ' , ' wait ' , is_flag = True , default = False ,
help = ' Wait for migrations to complete before returning. '
)
def node_unflush ( node , wait ) :
2018-07-18 22:58:41 -04:00
"""
Restore NODE to active service and migrate back all VMs . If unspecified , defaults to this host .
"""
2019-12-25 20:18:53 -05:00
retcode , retmsg = pvc_node . node_domain_state ( config , node , ' ready ' , wait )
cleanup ( retcode , retmsg )
2018-06-05 01:39:59 -04:00
2018-06-11 02:49:47 -04:00
###############################################################################
# pvc node info
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' info ' , short_help = ' Show details of a node object. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' node ' , default = myhostname
2018-06-11 02:49:47 -04:00
)
@click.option (
' -l ' , ' --long ' , ' long_output ' , is_flag = True , default = False ,
help = ' Display more detailed information. '
)
2018-06-16 22:22:07 -04:00
def node_info ( node , long_output ) :
2018-06-11 02:49:47 -04:00
"""
2018-06-16 22:22:07 -04:00
Show information about node NODE . If unspecified , defaults to this host .
2018-06-11 02:49:47 -04:00
"""
2019-12-26 11:20:57 -05:00
retcode , retdata = pvc_node . node_info ( config , node )
2019-05-10 23:25:06 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_node . format_info ( retdata , long_output )
2019-12-26 11:20:57 -05:00
cleanup ( retcode , retdata )
2018-06-11 02:49:47 -04:00
###############################################################################
# pvc node list
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' list ' , short_help = ' List all node objects. ' )
2018-07-19 21:58:11 -04:00
@click.argument (
' limit ' , default = None , required = False
)
def node_list ( limit ) :
2018-06-11 02:49:47 -04:00
"""
2020-01-04 14:06:36 -05:00
List all nodes ; optionally only match names matching regex LIMIT .
2018-06-11 02:49:47 -04:00
"""
2019-12-26 17:52:57 -05:00
retcode , retdata = pvc_node . node_list ( config , limit )
2019-05-10 23:25:06 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_node . format_list ( retdata )
2019-12-26 17:52:57 -05:00
cleanup ( retcode , retdata )
2018-06-11 02:49:47 -04:00
2018-06-05 01:39:59 -04:00
###############################################################################
# pvc vm
###############################################################################
2018-07-18 22:30:23 -04:00
@click.group ( name = ' vm ' , short_help = ' Manage a PVC virtual machine. ' , context_settings = CONTEXT_SETTINGS )
2018-09-20 03:25:58 -04:00
def cli_vm ( ) :
2018-06-05 01:39:59 -04:00
"""
Manage the state of a virtual machine in the PVC cluster .
"""
2020-01-02 11:19:11 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 11:19:11 -05:00
exit ( 1 )
2018-06-05 01:39:59 -04:00
###############################################################################
# pvc vm define
###############################################################################
@click.command ( name = ' define ' , short_help = ' Define a new virtual machine from a Libvirt XML file. ' )
@click.option (
2019-03-12 23:17:31 -04:00
' -t ' , ' --target ' , ' target_node ' ,
2019-10-12 01:17:39 -04:00
help = ' Home node for this domain; autoselect if unspecified. '
2018-07-18 12:15:39 -04:00
)
@click.option (
2019-10-12 01:17:39 -04:00
' -l ' , ' --limit ' , ' node_limit ' , default = None , show_default = False ,
help = ' Comma-separated list of nodes to limit VM operation to; saved with VM. '
)
@click.option (
' -s ' , ' --selector ' , ' node_selector ' , default = ' mem ' , show_default = True ,
2018-07-18 12:15:39 -04:00
type = click . Choice ( [ ' mem ' , ' load ' , ' vcpus ' , ' vms ' ] ) ,
2019-10-12 01:17:39 -04:00
help = ' Method to determine optimal target node during autoselect; saved with VM. '
)
@click.option (
2019-10-12 01:36:50 -04:00
' -a/-A ' , ' --autostart/--no-autostart ' , ' node_autostart ' , is_flag = True , default = False ,
2019-10-12 01:17:39 -04:00
help = ' Start VM automatically on next unflush/ready state of home node; unset by daemon once used. '
2018-06-05 01:39:59 -04:00
)
2018-06-16 22:22:07 -04:00
@click.argument (
2020-01-23 11:33:09 -05:00
' vmconfig ' , type = click . File ( )
2018-06-16 22:22:07 -04:00
)
2020-01-23 11:33:09 -05:00
def vm_define ( vmconfig , target_node , node_limit , node_selector , node_autostart ) :
2018-06-05 01:39:59 -04:00
"""
2020-01-23 11:33:09 -05:00
Define a new virtual machine from Libvirt XML configuration file VMCONFIG .
2018-06-05 01:39:59 -04:00
"""
2018-06-06 12:00:52 -04:00
# Open the XML file
2020-01-23 11:33:09 -05:00
vmconfig_data = vmconfig . read ( )
vmconfig . close ( )
2018-06-06 12:00:52 -04:00
2020-01-24 13:17:48 -05:00
# Verify our XML is sensible
try :
xml_data = etree . fromstring ( vmconfig_data )
new_cfg = etree . tostring ( xml_data , pretty_print = True ) . decode ( ' utf8 ' )
except :
cleanup ( False , ' Error: XML is malformed or invalid ' )
retcode , retmsg = pvc_vm . define_vm ( config , new_cfg , target_node , node_limit , node_selector , node_autostart )
2019-12-27 09:52:16 -05:00
cleanup ( retcode , retmsg )
2019-10-12 01:17:39 -04:00
###############################################################################
# pvc vm meta
###############################################################################
@click.command ( name = ' meta ' , short_help = ' Modify PVC metadata of an existing VM. ' )
@click.option (
' -l ' , ' --limit ' , ' node_limit ' , default = None , show_default = False ,
2019-10-12 02:08:52 -04:00
help = ' Comma-separated list of nodes to limit VM operation to; set to an empty string to remove. '
2019-10-12 01:17:39 -04:00
)
@click.option (
' -s ' , ' --selector ' , ' node_selector ' , default = None , show_default = False ,
type = click . Choice ( [ ' mem ' , ' load ' , ' vcpus ' , ' vms ' ] ) ,
2019-10-12 02:08:52 -04:00
help = ' Method to determine optimal target node during autoselect. '
2019-10-12 01:17:39 -04:00
)
@click.option (
2019-10-12 01:36:50 -04:00
' -a/-A ' , ' --autostart/--no-autostart ' , ' node_autostart ' , is_flag = True , default = None ,
2019-10-12 01:17:39 -04:00
help = ' Start VM automatically on next unflush/ready state of home node; unset by daemon once used. '
)
2020-01-30 11:45:46 -05:00
@click.option (
' -p ' , ' --profile ' , ' provisioner_profile ' , default = None , show_default = False ,
help = ' PVC provisioner profile name for VM. '
)
2019-10-12 01:17:39 -04:00
@click.argument (
' domain '
)
2020-01-30 11:45:46 -05:00
def vm_meta ( domain , node_limit , node_selector , node_autostart , provisioner_profile ) :
2019-10-12 01:17:39 -04:00
"""
Modify the PVC metadata of existing virtual machine DOMAIN . At least one option to update must be specified . DOMAIN may be a UUID or name .
"""
2020-01-30 11:45:46 -05:00
if node_limit is None and node_selector is None and node_autostart is None and provisioner_profile is None :
2019-10-12 01:17:39 -04:00
cleanup ( False , ' At least one metadata option must be specified to update. ' )
2020-01-30 11:45:46 -05:00
retcode , retmsg = pvc_vm . vm_metadata ( config , domain , node_limit , node_selector , node_autostart , provisioner_profile )
2019-12-27 09:52:16 -05:00
cleanup ( retcode , retmsg )
2018-06-06 12:00:52 -04:00
2018-07-20 00:38:31 -04:00
###############################################################################
# pvc vm modify
###############################################################################
@click.command ( name = ' modify ' , short_help = ' Modify an existing VM configuration. ' )
@click.option (
' -e ' , ' --editor ' , ' editor ' , is_flag = True ,
help = ' Use local editor to modify existing config. '
)
@click.option (
' -r ' , ' --restart ' , ' restart ' , is_flag = True ,
help = ' Immediately restart VM to apply new config. '
)
@click.argument (
' domain '
)
@click.argument (
2019-12-27 09:52:16 -05:00
' cfgfile ' , type = click . File ( ) , default = None , required = False
2018-07-20 00:38:31 -04:00
)
2019-12-27 09:52:16 -05:00
def vm_modify ( domain , cfgfile , editor , restart ) :
2018-07-20 00:38:31 -04:00
"""
Modify existing virtual machine DOMAIN , either in - editor or with replacement CONFIG . DOMAIN may be a UUID or name .
"""
2019-12-27 09:52:16 -05:00
if editor == False and cfgfile == None :
2018-09-20 03:25:58 -04:00
cleanup ( False , ' Either an XML config file or the " --editor " option must be specified. ' )
2018-07-20 00:38:31 -04:00
2019-12-27 09:52:16 -05:00
retcode , vm_information = pvc_vm . vm_info ( config , domain )
if not retcode and not vm_information . get ( ' name ' , None ) :
2020-01-04 14:06:36 -05:00
cleanup ( False , ' ERROR: Could not find VM " {} " ! ' . format ( domain ) )
2019-12-27 09:52:16 -05:00
dom_uuid = vm_information . get ( ' uuid ' )
dom_name = vm_information . get ( ' name ' )
2018-09-20 03:25:58 -04:00
2019-04-10 16:18:18 -04:00
if editor == True :
2018-07-20 00:38:31 -04:00
# Grab the current config
2019-12-27 09:52:16 -05:00
current_vm_cfg_raw = vm_information . get ( ' xml ' )
xml_data = etree . fromstring ( current_vm_cfg_raw )
2020-01-08 09:33:01 -05:00
current_vm_cfgfile = etree . tostring ( xml_data , pretty_print = True ) . decode ( ' utf8 ' ) . strip ( )
2018-07-20 00:38:31 -04:00
2020-01-08 09:33:01 -05:00
new_vm_cfgfile = click . edit ( text = current_vm_cfgfile , require_save = True , extension = ' .xml ' )
if new_vm_cfgfile is None :
2018-07-20 00:38:31 -04:00
click . echo ( ' Aborting with no modifications. ' )
exit ( 0 )
2020-01-08 09:33:01 -05:00
else :
new_vm_cfgfile = new_vm_cfgfile . strip ( )
2018-07-20 00:38:31 -04:00
2020-01-08 09:33:01 -05:00
# Show a diff and confirm
2018-07-20 00:38:31 -04:00
click . echo ( ' Pending modifications: ' )
click . echo ( ' ' )
2020-01-08 09:33:01 -05:00
diff = list ( difflib . unified_diff ( current_vm_cfgfile . split ( ' \n ' ) , new_vm_cfgfile . split ( ' \n ' ) , fromfile = ' current ' , tofile = ' modified ' , fromfiledate = ' ' , tofiledate = ' ' , n = 3 , lineterm = ' ' ) )
2018-07-20 00:38:31 -04:00
for line in diff :
if re . match ( ' ^ \ + ' , line ) != None :
click . echo ( colorama . Fore . GREEN + line + colorama . Fore . RESET )
elif re . match ( ' ^ \ - ' , line ) != None :
click . echo ( colorama . Fore . RED + line + colorama . Fore . RESET )
elif re . match ( ' ^ \ ^ ' , line ) != None :
click . echo ( colorama . Fore . BLUE + line + colorama . Fore . RESET )
else :
click . echo ( line )
click . echo ( ' ' )
2020-01-04 13:04:01 -05:00
click . confirm ( ' Write modifications to cluster? ' , abort = True )
2018-07-20 00:38:31 -04:00
2018-09-25 02:36:37 -04:00
if restart :
2019-12-27 09:52:16 -05:00
click . echo ( ' Writing modified configuration of VM " {} " and restarting. ' . format ( dom_name ) )
2018-09-25 02:36:37 -04:00
else :
2019-12-27 09:52:16 -05:00
click . echo ( ' Writing modified configuration of VM " {} " . ' . format ( dom_name ) )
2018-07-20 00:38:31 -04:00
# We're operating in replace mode
else :
# Open the XML file
2019-12-27 09:52:16 -05:00
new_vm_cfgfile = cfgfile . read ( )
cfgfile . close ( )
2018-07-20 00:38:31 -04:00
2018-09-25 02:36:37 -04:00
if restart :
2019-12-27 09:52:16 -05:00
click . echo ( ' Replacing configuration of VM " {} " with file " {} " and restarting. ' . format ( dom_name , cfgfile . name ) )
2018-09-25 02:36:37 -04:00
else :
2019-12-27 09:52:16 -05:00
click . echo ( ' Replacing configuration of VM " {} " with file " {} " . ' . format ( dom_name , cfgfile . name ) )
2018-07-20 00:38:31 -04:00
2020-01-24 13:17:48 -05:00
# Verify our XML is sensible
try :
xml_data = etree . fromstring ( new_vm_cfgfile )
new_cfg = etree . tostring ( xml_data , pretty_print = True ) . decode ( ' utf8 ' )
except Exception as e :
cleanup ( False , ' Error: XML is malformed or invalid: {} ' . format ( e ) )
retcode , retmsg = pvc_vm . vm_modify ( config , domain , new_cfg , restart )
2019-12-27 09:52:16 -05:00
cleanup ( retcode , retmsg )
2018-07-20 00:38:31 -04:00
2018-06-06 12:00:52 -04:00
###############################################################################
# pvc vm undefine
###############################################################################
2019-06-27 11:19:48 -04:00
@click.command ( name = ' undefine ' , short_help = ' Undefine a virtual machine. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-06 12:00:52 -04:00
)
2020-01-08 10:34:27 -05:00
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def vm_undefine ( domain , confirm_flag ) :
2018-06-06 12:00:52 -04:00
"""
2020-01-04 14:06:36 -05:00
Stop virtual machine DOMAIN and remove it database , preserving disks . DOMAIN may be a UUID or name .
2018-06-06 12:00:52 -04:00
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Undefine VM {} ' . format ( domain ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
2018-06-06 12:00:52 -04:00
2019-12-27 09:52:16 -05:00
retcode , retmsg = pvc_vm . vm_remove ( config , domain , delete_disks = False )
cleanup ( retcode , retmsg )
2018-06-05 01:39:59 -04:00
2019-06-27 11:19:48 -04:00
###############################################################################
# pvc vm remove
###############################################################################
@click.command ( name = ' remove ' , short_help = ' Remove a virtual machine. ' )
@click.argument (
' domain '
)
2020-01-08 10:34:27 -05:00
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def vm_remove ( domain , confirm_flag ) :
2019-06-27 11:19:48 -04:00
"""
2020-01-04 14:06:36 -05:00
Stop virtual machine DOMAIN and remove it , along with all disks , . DOMAIN may be a UUID or name .
2019-06-27 11:19:48 -04:00
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Undefine VM {} and remove all disks ' . format ( domain ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
2019-06-27 11:19:48 -04:00
2019-12-27 09:52:16 -05:00
retcode , retmsg = pvc_vm . vm_remove ( config , domain , delete_disks = True )
cleanup ( retcode , retmsg )
2019-03-12 21:09:54 -04:00
2018-06-05 01:39:59 -04:00
###############################################################################
# pvc vm start
###############################################################################
@click.command ( name = ' start ' , short_help = ' Start up a defined virtual machine. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-05 01:39:59 -04:00
)
2018-09-20 03:25:58 -04:00
def vm_start ( domain ) :
2018-06-05 01:39:59 -04:00
"""
2018-10-14 02:01:35 -04:00
Start virtual machine DOMAIN on its configured node . DOMAIN may be a UUID or name .
2018-06-05 01:39:59 -04:00
"""
2019-12-27 09:52:16 -05:00
retcode , retmsg = pvc_vm . vm_state ( config , domain , ' start ' )
cleanup ( retcode , retmsg )
2018-06-05 01:39:59 -04:00
2018-06-13 12:49:51 -04:00
###############################################################################
# pvc vm restart
###############################################################################
2018-06-17 02:24:06 -04:00
@click.command ( name = ' restart ' , short_help = ' Restart a running virtual machine. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-13 12:49:51 -04:00
)
2020-02-19 09:57:31 -05:00
@click.option (
' -w ' , ' --wait ' , ' wait ' , is_flag = True , default = False ,
help = ' Wait for restart to complete before returning. '
)
def vm_restart ( domain , wait ) :
2018-06-13 12:49:51 -04:00
"""
2018-06-17 02:24:06 -04:00
Restart running virtual machine DOMAIN . DOMAIN may be a UUID or name .
2018-06-13 12:49:51 -04:00
"""
2020-02-19 09:57:31 -05:00
retcode , retmsg = pvc_vm . vm_state ( config , domain , ' restart ' , wait = wait )
2019-12-27 09:52:16 -05:00
cleanup ( retcode , retmsg )
2018-06-13 12:49:51 -04:00
2018-06-05 01:39:59 -04:00
###############################################################################
# pvc vm shutdown
###############################################################################
@click.command ( name = ' shutdown ' , short_help = ' Gracefully shut down a running virtual machine. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
2020-01-08 10:34:27 -05:00
' domain '
2018-06-05 01:39:59 -04:00
)
2020-02-19 09:57:31 -05:00
@click.option (
' -w ' , ' --wait ' , ' wait ' , is_flag = True , default = False ,
help = ' Wait for shutdown to complete before returning. '
)
def vm_shutdown ( domain , wait ) :
2018-06-05 01:39:59 -04:00
"""
2018-06-16 22:22:07 -04:00
Gracefully shut down virtual machine DOMAIN . DOMAIN may be a UUID or name .
2018-06-05 01:39:59 -04:00
"""
2020-02-19 09:57:31 -05:00
retcode , retmsg = pvc_vm . vm_state ( config , domain , ' shutdown ' , wait = wait )
2019-12-27 09:52:16 -05:00
cleanup ( retcode , retmsg )
2018-06-05 01:39:59 -04:00
###############################################################################
# pvc vm stop
###############################################################################
@click.command ( name = ' stop ' , short_help = ' Forcibly halt a running virtual machine. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-05 01:39:59 -04:00
)
2018-09-20 03:25:58 -04:00
def vm_stop ( domain ) :
2018-06-05 01:39:59 -04:00
"""
2018-06-16 22:22:07 -04:00
Forcibly halt ( destroy ) running virtual machine DOMAIN . DOMAIN may be a UUID or name .
2018-06-05 01:39:59 -04:00
"""
2019-12-27 09:52:16 -05:00
retcode , retmsg = pvc_vm . vm_state ( config , domain , ' stop ' )
cleanup ( retcode , retmsg )
2018-06-05 01:39:59 -04:00
2019-10-23 23:37:42 -04:00
###############################################################################
# pvc vm disable
###############################################################################
@click.command ( name = ' disable ' , short_help = ' Mark a virtual machine as disabled. ' )
@click.argument (
' domain '
)
def vm_disable ( domain ) :
"""
Prevent stopped virtual machine DOMAIN from being counted towards cluster health status . DOMAIN may be a UUID or name .
Use this option for VM that are stopped intentionally or long - term and which should not impact cluster health if stopped . A VM can be started directly from disable state .
"""
2019-12-27 09:52:16 -05:00
retcode , retmsg = pvc_vm . vm_state ( config , domain , ' disable ' )
cleanup ( retcode , retmsg )
2019-10-23 23:37:42 -04:00
2018-06-10 21:03:41 -04:00
###############################################################################
# pvc vm move
###############################################################################
@click.command ( name = ' move ' , short_help = ' Permanently move a virtual machine to another node. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
2020-01-08 10:34:27 -05:00
' domain '
2018-06-10 21:03:41 -04:00
)
@click.option (
2019-03-12 23:17:31 -04:00
' -t ' , ' --target ' , ' target_node ' , default = None ,
2018-10-14 02:01:35 -04:00
help = ' Target node to migrate to; autodetect if unspecified. '
2018-06-10 21:03:41 -04:00
)
2020-02-19 09:57:31 -05:00
@click.option (
' -w ' , ' --wait ' , ' wait ' , is_flag = True , default = False ,
help = ' Wait for migration to complete before returning. '
)
def vm_move ( domain , target_node , wait ) :
2018-06-10 21:03:41 -04:00
"""
2018-10-14 02:01:35 -04:00
Permanently move virtual machine DOMAIN , via live migration if running and possible , to another node . DOMAIN may be a UUID or name .
2018-06-10 21:03:41 -04:00
"""
2020-02-19 09:57:31 -05:00
retcode , retmsg = pvc_vm . vm_node ( config , domain , target_node , ' move ' , force = False , wait = wait )
2019-12-27 09:52:16 -05:00
cleanup ( retcode , retmsg )
2018-06-10 21:03:41 -04:00
2018-06-05 22:06:08 -04:00
###############################################################################
# pvc vm migrate
###############################################################################
2018-06-22 12:24:53 -04:00
@click.command ( name = ' migrate ' , short_help = ' Temporarily migrate a virtual machine to another node. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-05 22:06:08 -04:00
)
@click.option (
2019-03-12 23:17:31 -04:00
' -t ' , ' --target ' , ' target_node ' , default = None ,
2018-10-14 02:01:35 -04:00
help = ' Target node to migrate to; autodetect if unspecified. '
2018-06-05 22:06:08 -04:00
)
@click.option (
' -f ' , ' --force ' , ' force_migrate ' , is_flag = True , default = False ,
2019-07-07 15:10:48 -04:00
help = ' Force migrate an already migrated VM; does not replace an existing previous node value. '
2018-06-05 22:06:08 -04:00
)
2020-02-19 09:57:31 -05:00
@click.option (
' -w ' , ' --wait ' , ' wait ' , is_flag = True , default = False ,
help = ' Wait for migration to complete before returning. '
)
def vm_migrate ( domain , target_node , force_migrate , wait ) :
2018-06-05 01:39:59 -04:00
"""
2018-10-14 02:01:35 -04:00
Temporarily migrate running virtual machine DOMAIN , via live migration if possible , to another node . DOMAIN may be a UUID or name . If DOMAIN is not running , it will be started on the target node .
2018-06-05 01:39:59 -04:00
"""
2020-02-19 09:57:31 -05:00
retcode , retmsg = pvc_vm . vm_node ( config , domain , target_node , ' migrate ' , force = force_migrate , wait = wait )
2019-12-27 09:52:16 -05:00
cleanup ( retcode , retmsg )
2018-06-05 22:06:08 -04:00
###############################################################################
# pvc vm unmigrate
###############################################################################
2018-06-05 01:39:59 -04:00
@click.command ( name = ' unmigrate ' , short_help = ' Restore a migrated virtual machine to its original node. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-05 22:06:08 -04:00
)
2020-02-19 09:57:31 -05:00
@click.option (
' -w ' , ' --wait ' , ' wait ' , is_flag = True , default = False ,
help = ' Wait for migration to complete before returning. '
)
def vm_unmigrate ( domain , wait ) :
2018-06-05 01:39:59 -04:00
"""
2018-10-14 02:01:35 -04:00
Restore previously migrated virtual machine DOMAIN , via live migration if possible , to its original node . DOMAIN may be a UUID or name . If DOMAIN is not running , it will be started on the target node .
2018-06-05 01:39:59 -04:00
"""
2020-02-19 09:57:31 -05:00
retcode , retmsg = pvc_vm . vm_node ( config , domain , None , ' unmigrate ' , force = False , wait = wait )
2019-12-27 09:52:16 -05:00
cleanup ( retcode , retmsg )
2018-06-05 22:06:08 -04:00
2019-08-07 13:42:01 -04:00
###############################################################################
2019-08-07 17:50:25 -04:00
# pvc vm flush-locks
2019-08-07 13:42:01 -04:00
###############################################################################
2019-08-07 17:50:25 -04:00
@click.command ( name = ' flush-locks ' , short_help = ' Flush stale RBD locks for a virtual machine. ' )
2019-08-07 13:42:01 -04:00
@click.argument (
' domain '
)
def vm_flush_locks ( domain ) :
"""
Flush stale RBD locks for virtual machine DOMAIN . DOMAIN may be a UUID or name . DOMAIN must be in a stopped state before flushing locks .
"""
2019-12-27 09:52:16 -05:00
retcode , retmsg = pvc_vm . vm_locks ( config , domain )
cleanup ( retcode , retmsg )
###############################################################################
# pvc vm log
###############################################################################
@click.command ( name = ' log ' , short_help = ' Show console logs of a VM object. ' )
@click.argument (
' domain '
)
@click.option (
2020-01-08 10:06:34 -05:00
' -l ' , ' --lines ' , ' lines ' , default = 1000 , show_default = True ,
2019-12-27 09:52:16 -05:00
help = ' Display this many log lines from the end of the log buffer. '
)
@click.option (
' -f ' , ' --follow ' , ' follow ' , is_flag = True , default = False ,
help = ' Follow the log buffer; output may be delayed by a few seconds relative to the live system. The --lines value defaults to 10 for the initial output. '
)
def vm_log ( domain , lines , follow ) :
"""
2020-01-08 10:34:27 -05:00
Show console logs of virtual machine DOMAIN on its current node in a pager or continuously . DOMAIN may be a UUID or name . Note that migrating a VM to a different node will cause the log buffer to be overwritten by entries from the new node .
2019-12-27 09:52:16 -05:00
"""
if follow :
retcode , retmsg = pvc_vm . follow_console_log ( config , domain , lines )
else :
retcode , retmsg = pvc_vm . view_console_log ( config , domain , lines )
2020-01-08 10:06:34 -05:00
click . echo_via_pager ( retmsg )
retmsg = ' '
2019-12-27 09:52:16 -05:00
cleanup ( retcode , retmsg )
2019-08-07 13:42:01 -04:00
2018-06-05 22:06:08 -04:00
###############################################################################
2018-06-11 01:24:14 -04:00
# pvc vm info
2018-06-05 22:06:08 -04:00
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' info ' , short_help = ' Show details of a VM object. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-05 01:39:59 -04:00
)
2018-06-05 18:45:54 -04:00
@click.option (
' -l ' , ' --long ' , ' long_output ' , is_flag = True , default = False ,
help = ' Display more detailed information. '
)
2018-06-16 22:22:07 -04:00
def vm_info ( domain , long_output ) :
2018-06-05 01:39:59 -04:00
"""
2020-01-08 10:34:27 -05:00
Show information about virtual machine DOMAIN . DOMAIN may be a UUID or name .
2018-06-05 01:39:59 -04:00
"""
2018-06-05 22:06:08 -04:00
2019-12-27 09:52:16 -05:00
retcode , retdata = pvc_vm . vm_info ( config , domain )
2019-05-20 22:15:28 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_vm . format_info ( config , retdata , long_output )
2019-12-27 09:52:16 -05:00
cleanup ( retcode , retdata )
2018-06-05 01:39:59 -04:00
2019-04-11 19:06:06 -04:00
###############################################################################
2019-12-27 09:52:16 -05:00
# pvc vm dump
2019-04-11 19:06:06 -04:00
###############################################################################
2019-12-27 09:52:16 -05:00
@click.command ( name = ' dump ' , short_help = ' Dump a virtual machine XML to stdout. ' )
2019-04-11 19:06:06 -04:00
@click.argument (
' domain '
)
2019-12-27 09:52:16 -05:00
def vm_dump ( domain ) :
2019-04-11 19:06:06 -04:00
"""
2019-12-27 09:52:16 -05:00
Dump the Libvirt XML definition of virtual machine DOMAIN to stdout . DOMAIN may be a UUID or name .
2019-04-11 19:06:06 -04:00
"""
2019-12-27 09:52:16 -05:00
retcode , vm_information = pvc_vm . vm_info ( config , domain )
if not retcode and not vm_information . get ( ' name ' , None ) :
2020-01-04 14:06:36 -05:00
cleanup ( False , ' ERROR: Could not find VM " {} " ! ' . format ( domain ) )
2019-12-27 09:52:16 -05:00
# Grab the current config
current_vm_cfg_raw = vm_information . get ( ' xml ' )
xml_data = etree . fromstring ( current_vm_cfg_raw )
current_vm_cfgfile = etree . tostring ( xml_data , pretty_print = True ) . decode ( ' utf8 ' )
click . echo ( current_vm_cfgfile . strip ( ) )
2019-04-11 19:06:06 -04:00
2018-06-10 20:21:00 -04:00
###############################################################################
2018-06-11 01:24:14 -04:00
# pvc vm list
2018-06-10 20:21:00 -04:00
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' list ' , short_help = ' List all VM objects. ' )
2018-07-19 21:58:11 -04:00
@click.argument (
' limit ' , default = None , required = False
)
2018-06-11 01:24:14 -04:00
@click.option (
2019-03-12 23:17:31 -04:00
' -t ' , ' --target ' , ' target_node ' , default = None ,
2019-03-20 11:31:54 -04:00
help = ' Limit list to VMs on the specified node. '
)
@click.option (
' -s ' , ' --state ' , ' target_state ' , default = None ,
help = ' Limit list to VMs in the specified state. '
2018-06-11 01:24:14 -04:00
)
2019-03-12 21:30:01 -04:00
@click.option (
' -r ' , ' --raw ' , ' raw ' , is_flag = True , default = False ,
2019-03-20 11:31:54 -04:00
help = ' Display the raw list of VM names only. '
2019-03-12 21:30:01 -04:00
)
2019-03-20 11:31:54 -04:00
def vm_list ( target_node , target_state , limit , raw ) :
2018-07-18 22:58:41 -04:00
"""
2020-01-04 14:06:36 -05:00
List all virtual machines ; optionally only match names matching regex LIMIT .
2018-10-20 15:27:07 -04:00
NOTE : Red - coloured network lists indicate one or more configured networks are missing / invalid .
2018-07-18 22:58:41 -04:00
"""
2019-12-27 09:52:16 -05:00
retcode , retdata = pvc_vm . vm_list ( config , limit , target_node , target_state )
2019-05-20 22:15:28 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_vm . format_list ( config , retdata , raw )
2019-12-27 09:52:16 -05:00
cleanup ( retcode , retdata )
2018-06-10 20:21:00 -04:00
2018-09-21 23:43:30 -04:00
###############################################################################
# pvc network
###############################################################################
@click.group ( name = ' network ' , short_help = ' Manage a PVC virtual network. ' , context_settings = CONTEXT_SETTINGS )
def cli_network ( ) :
"""
Manage the state of a VXLAN network in the PVC cluster .
"""
2020-01-02 11:19:11 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 11:19:11 -05:00
exit ( 1 )
2018-09-21 23:43:30 -04:00
###############################################################################
# pvc network add
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' add ' , short_help = ' Add a new virtual network. ' )
2018-09-21 23:43:30 -04:00
@click.option (
' -d ' , ' --description ' , ' description ' ,
2018-10-17 00:23:27 -04:00
required = True ,
2018-11-13 01:22:15 -05:00
help = ' Description of the network; must be unique and not contain whitespace. '
2018-09-28 20:42:24 -04:00
)
@click.option (
2019-03-15 11:28:49 -04:00
' -p ' , ' --type ' , ' nettype ' ,
2018-09-28 20:42:24 -04:00
required = True ,
2019-03-15 11:28:49 -04:00
type = click . Choice ( [ ' managed ' , ' bridged ' ] ) ,
help = ' Network type; managed networks control IP addressing; bridged networks are simple vLAN bridges. All subsequent options are unused for bridged networks. '
)
@click.option (
' -n ' , ' --domain ' , ' domain ' ,
default = None ,
2018-09-28 20:42:24 -04:00
help = ' Domain name of the network. '
2018-09-21 23:43:30 -04:00
)
2019-12-08 23:32:03 -05:00
@click.option (
' --dns-server ' , ' name_servers ' ,
multiple = True ,
2019-12-08 23:59:17 -05:00
help = ' DNS nameserver for network; multiple entries may be specified. '
2019-12-08 23:32:03 -05:00
)
2018-09-21 23:43:30 -04:00
@click.option (
' -i ' , ' --ipnet ' , ' ip_network ' ,
2018-11-13 01:22:15 -05:00
default = None ,
help = ' CIDR-format IPv4 network address for subnet. '
)
@click.option (
' -i6 ' , ' --ipnet6 ' , ' ip6_network ' ,
default = None ,
help = ' CIDR-format IPv6 network address for subnet; should be /64 or larger ending " ::/YY " . '
2018-09-21 23:43:30 -04:00
)
@click.option (
' -g ' , ' --gateway ' , ' ip_gateway ' ,
2018-11-13 01:22:15 -05:00
default = None ,
help = ' Default IPv4 gateway address for subnet. '
)
@click.option (
' -g6 ' , ' --gateway6 ' , ' ip6_gateway ' ,
default = None ,
help = ' Default IPv6 gateway address for subnet. [default: " X::1 " ] '
2018-09-21 23:43:30 -04:00
)
@click.option (
2018-09-23 15:26:20 -04:00
' --dhcp/--no-dhcp ' , ' dhcp_flag ' ,
2018-09-21 23:43:30 -04:00
is_flag = True ,
2019-03-15 11:28:49 -04:00
default = False ,
2018-11-13 01:22:15 -05:00
help = ' Enable/disable IPv4 DHCP for clients on subnet. '
2018-09-21 23:43:30 -04:00
)
2018-09-29 02:54:48 -04:00
@click.option (
' --dhcp-start ' , ' dhcp_start ' ,
default = None ,
2018-11-13 01:22:15 -05:00
help = ' IPv4 DHCP range start address. '
2018-09-29 02:54:48 -04:00
)
@click.option (
' --dhcp-end ' , ' dhcp_end ' ,
default = None ,
2018-11-13 01:22:15 -05:00
help = ' IPv4 DHCP range end address. '
2018-09-29 02:54:48 -04:00
)
2018-09-21 23:43:30 -04:00
@click.argument (
' vni '
)
2019-12-08 23:32:03 -05:00
def net_add ( vni , description , nettype , domain , ip_network , ip_gateway , ip6_network , ip6_gateway , dhcp_flag , dhcp_start , dhcp_end , name_servers ) :
2018-09-21 23:43:30 -04:00
"""
2020-01-04 14:06:36 -05:00
Add a new virtual network with VXLAN identifier VNI .
2018-09-21 23:43:30 -04:00
2019-03-15 11:28:49 -04:00
Examples :
pvc network add 101 - - type bridged
> Creates vLAN 101 and a simple bridge on the VNI dev interface .
2019-12-30 13:29:07 -05:00
2019-03-15 11:28:49 -04:00
pvc network add 1001 - - type managed - - domain test . local - - ipnet 10.1 .1 .0 / 24 - - gateway 10.1 .1 .1
> Creates a VXLAN with ID 1001 on the VNI dev interface , with IPv4 managed networking .
2018-11-13 01:22:15 -05:00
IPv6 is fully supported with - - ipnet6 and - - gateway6 in addition to or instead of IPv4 . PVC will configure DHCPv6 in a semi - managed configuration for the network if set .
2018-09-21 23:43:30 -04:00
"""
2019-03-15 11:28:49 -04:00
if nettype == ' managed ' and not ip_network and not ip6_network :
2018-11-13 01:22:15 -05:00
click . echo ( ' Error: At least one of " -i " / " --ipnet " or " -i6 " / " --ipnet6 " must be specified. ' )
2019-03-15 11:28:49 -04:00
exit ( 1 )
2018-11-13 01:22:15 -05:00
2019-12-29 16:13:32 -05:00
retcode , retmsg = pvc_network . net_add ( config , vni , description , nettype , domain , name_servers , ip_network , ip_gateway , ip6_network , ip6_gateway , dhcp_flag , dhcp_start , dhcp_end )
cleanup ( retcode , retmsg )
2018-09-21 23:43:30 -04:00
2018-09-23 15:26:20 -04:00
###############################################################################
# pvc network modify
###############################################################################
@click.command ( name = ' modify ' , short_help = ' Modify an existing virtual network. ' )
@click.option (
' -d ' , ' --description ' , ' description ' ,
default = None ,
2018-10-17 00:23:27 -04:00
help = ' Description of the network; must be unique and not contain whitespace. '
2018-09-23 15:26:20 -04:00
)
2018-10-03 20:22:42 -04:00
@click.option (
' -n ' , ' --domain ' , ' domain ' ,
default = None ,
help = ' Domain name of the network. '
)
2019-12-08 23:32:03 -05:00
@click.option (
' --dns-server ' , ' name_servers ' ,
multiple = True ,
2019-12-08 23:59:17 -05:00
help = ' DNS nameserver for network; multiple entries may be specified (will overwrite all previous entries). '
2019-12-08 23:32:03 -05:00
)
2018-09-23 15:26:20 -04:00
@click.option (
2018-11-14 00:19:43 -05:00
' -i ' , ' --ipnet ' , ' ip4_network ' ,
2018-09-23 15:26:20 -04:00
default = None ,
2018-11-13 01:22:15 -05:00
help = ' CIDR-format IPv4 network address for subnet. '
)
@click.option (
' -i6 ' , ' --ipnet6 ' , ' ip6_network ' ,
default = None ,
help = ' CIDR-format IPv6 network address for subnet. '
2018-09-23 15:26:20 -04:00
)
@click.option (
2018-11-14 00:19:43 -05:00
' -g ' , ' --gateway ' , ' ip4_gateway ' ,
2018-09-23 15:26:20 -04:00
default = None ,
2018-11-13 01:22:15 -05:00
help = ' Default IPv4 gateway address for subnet. '
)
@click.option (
' -g6 ' , ' --gateway6 ' , ' ip6_gateway ' ,
default = None ,
help = ' Default IPv6 gateway address for subnet. '
2018-09-23 15:26:20 -04:00
)
@click.option (
' --dhcp/--no-dhcp ' , ' dhcp_flag ' ,
is_flag = True ,
2018-10-17 00:23:27 -04:00
default = None ,
2018-09-23 15:26:20 -04:00
help = ' Enable/disable DHCP for clients on subnet. '
)
2018-10-03 20:22:42 -04:00
@click.option (
' --dhcp-start ' , ' dhcp_start ' ,
default = None ,
help = ' DHCP range start address. '
)
@click.option (
' --dhcp-end ' , ' dhcp_end ' ,
default = None ,
help = ' DHCP range end address. '
)
2018-09-23 15:26:20 -04:00
@click.argument (
' vni '
)
2019-12-08 23:32:03 -05:00
def net_modify ( vni , description , domain , name_servers , ip6_network , ip6_gateway , ip4_network , ip4_gateway , dhcp_flag , dhcp_start , dhcp_end ) :
2018-09-23 15:26:20 -04:00
"""
Modify details of virtual network VNI . All fields optional ; only specified fields will be updated .
Example :
2018-10-03 20:22:42 -04:00
pvc network modify 1001 - - gateway 10.1 .1 .1 - - dhcp
2018-09-23 15:26:20 -04:00
"""
2019-12-29 16:13:32 -05:00
retcode , retmsg = pvc_network . net_modify ( config , vni , description , domain , name_servers , ip4_network , ip4_gateway , ip6_network , ip6_gateway , dhcp_flag , dhcp_start , dhcp_end )
cleanup ( retcode , retmsg )
2018-09-23 15:26:20 -04:00
2018-09-21 23:43:30 -04:00
###############################################################################
# pvc network remove
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' remove ' , short_help = ' Remove a virtual network. ' )
2018-09-21 23:43:30 -04:00
@click.argument (
' net '
)
2020-01-08 10:34:27 -05:00
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def net_remove ( net , confirm_flag ) :
2018-09-21 23:43:30 -04:00
"""
2020-01-04 14:06:36 -05:00
Remove an existing virtual network NET ; NET must be a VNI .
2018-09-21 23:43:30 -04:00
WARNING : PVC does not verify whether clients are still present in this network . Before removing , ensure
that all client VMs have been removed from the network or undefined behaviour may occur .
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove network {} ' . format ( net ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
2018-09-21 23:43:30 -04:00
2019-12-29 16:13:32 -05:00
retcode , retmsg = pvc_network . net_remove ( config , net )
cleanup ( retcode , retmsg )
2018-09-21 23:43:30 -04:00
###############################################################################
# pvc network info
###############################################################################
@click.command ( name = ' info ' , short_help = ' Show details of a network. ' )
@click.argument (
' vni '
)
2018-09-25 01:32:03 -04:00
@click.option (
' -l ' , ' --long ' , ' long_output ' , is_flag = True , default = False ,
help = ' Display more detailed information. '
)
def net_info ( vni , long_output ) :
2018-09-21 23:43:30 -04:00
"""
2020-01-08 10:34:27 -05:00
Show information about virtual network VNI .
2018-09-21 23:43:30 -04:00
"""
2019-12-29 16:13:32 -05:00
retcode , retdata = pvc_network . net_info ( config , vni )
2019-07-04 23:01:22 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_network . format_info ( config , retdata , long_output )
2019-12-29 16:13:32 -05:00
cleanup ( retcode , retdata )
2018-09-21 23:43:30 -04:00
###############################################################################
# pvc network list
###############################################################################
@click.command ( name = ' list ' , short_help = ' List all VM objects. ' )
@click.argument (
' limit ' , default = None , required = False
)
def net_list ( limit ) :
"""
2020-01-04 14:06:36 -05:00
List all virtual networks ; optionally only match VNIs or Descriptions matching regex LIMIT .
2018-09-21 23:43:30 -04:00
"""
2019-12-29 16:13:32 -05:00
retcode , retdata = pvc_network . net_list ( config , limit )
2019-07-04 23:01:22 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_network . format_list ( config , retdata )
2019-12-29 16:13:32 -05:00
cleanup ( retcode , retdata )
2018-09-21 23:43:30 -04:00
2018-09-28 20:31:56 -04:00
###############################################################################
# pvc network dhcp
###############################################################################
2018-11-13 01:22:15 -05:00
@click.group ( name = ' dhcp ' , short_help = ' Manage IPv4 DHCP leases in a PVC virtual network. ' , context_settings = CONTEXT_SETTINGS )
2018-09-28 20:31:56 -04:00
def net_dhcp ( ) :
"""
2018-11-13 01:22:15 -05:00
Manage host IPv4 DHCP leases of a VXLAN network in the PVC cluster .
2018-09-28 20:31:56 -04:00
"""
2020-01-02 11:19:11 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 11:19:11 -05:00
exit ( 1 )
2018-09-28 20:31:56 -04:00
2018-10-03 19:23:46 -04:00
###############################################################################
2019-12-29 16:13:32 -05:00
# pvc network dhcp add
2018-10-03 19:23:46 -04:00
###############################################################################
@click.command ( name = ' add ' , short_help = ' Add a DHCP static reservation. ' )
2018-09-28 20:31:56 -04:00
@click.argument (
' net '
)
@click.argument (
' ipaddr '
)
@click.argument (
2018-10-02 00:08:45 -04:00
' hostname '
2018-09-28 20:31:56 -04:00
)
2018-09-30 12:43:56 -04:00
@click.argument (
2018-10-02 00:08:45 -04:00
' macaddr '
2018-09-30 12:43:56 -04:00
)
2019-12-29 16:13:32 -05:00
def net_dhcp_add ( net , ipaddr , macaddr , hostname ) :
2018-09-28 20:31:56 -04:00
"""
2019-12-29 16:13:32 -05:00
Add a new DHCP static reservation of IP address IPADDR with hostname HOSTNAME for MAC address MACADDR to virtual network NET ; NET must be a VNI .
2018-09-28 20:31:56 -04:00
"""
2019-12-29 16:13:32 -05:00
retcode , retmsg = pvc_network . net_dhcp_add ( config , net , ipaddr , macaddr , hostname )
cleanup ( retcode , retmsg )
2018-09-28 20:31:56 -04:00
###############################################################################
2019-12-29 16:13:32 -05:00
# pvc network dhcp remove
2018-09-28 20:31:56 -04:00
###############################################################################
2018-10-03 19:23:46 -04:00
@click.command ( name = ' remove ' , short_help = ' Remove a DHCP static reservation. ' )
2018-09-28 20:31:56 -04:00
@click.argument (
' net '
)
@click.argument (
2019-12-29 16:13:32 -05:00
' macaddr '
2018-09-28 20:31:56 -04:00
)
2020-01-08 10:34:27 -05:00
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def net_dhcp_remove ( net , macaddr , confirm_flag ) :
2018-09-28 20:31:56 -04:00
"""
2020-01-08 10:34:27 -05:00
Remove a DHCP lease for MACADDR from virtual network NET ; NET must be a VNI .
2018-09-28 20:31:56 -04:00
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove DHCP lease for {} in network {} ' . format ( macaddr , net ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
2018-09-28 20:31:56 -04:00
2020-01-05 00:49:50 -05:00
retcode , retmsg = pvc_network . net_dhcp_remove ( config , net , macaddr )
2019-12-29 16:13:32 -05:00
cleanup ( retcode , retmsg )
2018-09-28 20:31:56 -04:00
###############################################################################
2019-12-29 16:13:32 -05:00
# pvc network dhcp list
2018-09-28 20:31:56 -04:00
###############################################################################
2019-12-29 16:13:32 -05:00
@click.command ( name = ' list ' , short_help = ' List active DHCP leases. ' )
2018-09-28 20:31:56 -04:00
@click.argument (
' net '
)
@click.argument (
' limit ' , default = None , required = False
)
2019-12-29 16:13:32 -05:00
@click.option (
' -s ' , ' --static ' , ' only_static ' , is_flag = True , default = False ,
help = ' Show only static leases. '
)
def net_dhcp_list ( net , limit , only_static ) :
2018-09-28 20:31:56 -04:00
"""
2019-12-29 16:13:32 -05:00
List all DHCP leases in virtual network NET ; optionally only match elements matching regex LIMIT ; NET must be a VNI .
2018-09-28 20:31:56 -04:00
"""
2019-12-29 16:13:32 -05:00
retcode , retdata = pvc_network . net_dhcp_list ( config , net , limit , only_static )
2019-07-04 23:01:22 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_network . format_list_dhcp ( retdata )
2019-12-29 16:13:32 -05:00
cleanup ( retcode , retdata )
2018-09-28 20:31:56 -04:00
###############################################################################
# pvc network acl
###############################################################################
@click.group ( name = ' acl ' , short_help = ' Manage a PVC virtual network firewall ACL rule. ' , context_settings = CONTEXT_SETTINGS )
def net_acl ( ) :
"""
Manage firewall ACLs of a VXLAN network in the PVC cluster .
"""
2020-01-02 11:19:11 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 11:19:11 -05:00
exit ( 1 )
2018-09-21 23:43:30 -04:00
2018-10-17 00:23:27 -04:00
###############################################################################
# pvc network acl add
###############################################################################
@click.command ( name = ' add ' , short_help = ' Add firewall ACL. ' )
@click.option (
' --in/--out ' , ' direction ' ,
is_flag = True ,
2019-12-29 16:13:32 -05:00
default = True , #inbound
2018-10-17 00:23:27 -04:00
help = ' Inbound or outbound ruleset. '
)
@click.option (
' -d ' , ' --description ' , ' description ' ,
required = True ,
help = ' Description of the ACL; must be unique and not contain whitespace. '
)
@click.option (
' -r ' , ' --rule ' , ' rule ' ,
required = True ,
help = ' NFT firewall rule. '
)
@click.option (
' -o ' , ' --order ' , ' order ' ,
default = None ,
help = ' Order of rule in the chain (see " list " ); defaults to last. '
)
@click.argument (
' net '
)
def net_acl_add ( net , direction , description , rule , order ) :
"""
2019-12-29 16:13:32 -05:00
Add a new NFT firewall rule to network NET ; the rule is a literal NFT rule belonging to the forward table for the client network ; NET must be a VNI .
2018-10-17 00:23:27 -04:00
NOTE : All client networks are default - allow in both directions ; deny rules MUST be added here at the end of the sequence for a default - deny setup .
NOTE : Ordering places the rule at the specified ID , not before it ; the old rule of that ID and all subsequent rules will be moved down .
2019-12-29 16:13:32 -05:00
NOTE : Descriptions are used as names , and must be unique within a network ( both directions ) .
2018-10-17 00:23:27 -04:00
Example :
pvc network acl add 1001 - - in - - rule " tcp dport 22 ct state new accept " - - description " ssh-in " - - order 3
"""
2019-12-29 16:13:32 -05:00
if direction :
direction = ' in '
else :
direction = ' out '
2018-10-17 00:23:27 -04:00
2019-12-29 16:13:32 -05:00
retcode , retmsg = pvc_network . net_acl_add ( config , net , direction , description , rule , order )
cleanup ( retcode , retmsg )
2018-10-17 00:23:27 -04:00
###############################################################################
# pvc network acl remove
###############################################################################
@click.command ( name = ' remove ' , short_help = ' Remove firewall ACL. ' )
@click.argument (
' net '
)
@click.argument (
' rule ' ,
)
2020-01-08 10:34:27 -05:00
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def net_acl_remove ( net , rule , confirm_flag ) :
2018-10-17 00:23:27 -04:00
"""
2019-12-29 16:13:32 -05:00
Remove an NFT firewall rule RULE from network NET ; RULE must be a description ; NET must be a VNI .
2018-10-17 00:23:27 -04:00
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove ACL {} in network {} ' . format ( rule , net ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
2018-10-17 00:23:27 -04:00
2019-12-29 16:13:32 -05:00
retcode , retmsg = pvc_network . net_acl_remove ( config , net , rule )
cleanup ( retcode , retmsg )
2018-10-17 00:23:27 -04:00
###############################################################################
# pvc network acl list
###############################################################################
@click.command ( name = ' list ' , short_help = ' List firewall ACLs. ' )
@click.option (
' --in/--out ' , ' direction ' ,
is_flag = True ,
2018-10-17 20:05:22 -04:00
required = False ,
2018-10-17 00:23:27 -04:00
default = None ,
2018-10-17 20:05:22 -04:00
help = ' Inbound or outbound rule set only. '
2018-10-17 00:23:27 -04:00
)
@click.argument (
' net '
)
@click.argument (
' limit ' , default = None , required = False
)
def net_acl_list ( net , limit , direction ) :
"""
List all NFT firewall rules in network NET ; optionally only match elements matching description regex LIMIT ; NET can be either a VNI or description .
"""
2019-12-29 16:13:32 -05:00
if direction is not None :
if direction :
direction = ' in '
else :
direction = ' out '
2018-09-21 23:43:30 -04:00
2019-12-29 16:13:32 -05:00
retcode , retdata = pvc_network . net_acl_list ( config , net , limit , direction )
2019-07-04 23:01:22 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_network . format_list_acl ( retdata )
2019-12-29 16:13:32 -05:00
cleanup ( retcode , retdata )
2018-09-21 23:43:30 -04:00
2018-10-27 18:11:58 -04:00
###############################################################################
2019-07-10 15:14:17 -04:00
# pvc storage
###############################################################################
# Note: The prefix `storage` allows future potential storage subsystems.
# Since Ceph is the only section not abstracted by PVC directly
# (i.e. it references Ceph-specific concepts), this makes more
# sense in the long-term.
###############################################################################
@click.group ( name = ' storage ' , short_help = ' Manage the PVC storage cluster. ' , context_settings = CONTEXT_SETTINGS )
def cli_storage ( ) :
"""
Manage the storage of the PVC cluster .
"""
2020-01-02 11:19:11 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 11:19:11 -05:00
exit ( 1 )
2019-07-10 15:14:17 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage status
2018-10-27 18:11:58 -04:00
###############################################################################
@click.command ( name = ' status ' , short_help = ' Show storage cluster status. ' )
def ceph_status ( ) :
"""
Show detailed status of the storage cluster .
"""
2019-12-29 20:33:51 -05:00
retcode , retdata = pvc_ceph . ceph_status ( config )
2019-07-05 00:44:40 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_ceph . format_raw_output ( retdata )
2019-12-29 20:33:51 -05:00
cleanup ( retcode , retdata )
2019-07-08 10:56:33 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage util
2019-07-08 10:56:33 -04:00
###############################################################################
2019-12-29 20:33:51 -05:00
@click.command ( name = ' util ' , short_help = ' Show storage cluster utilization. ' )
def ceph_util ( ) :
2019-07-08 10:56:33 -04:00
"""
Show utilization of the storage cluster .
"""
2019-12-29 20:33:51 -05:00
retcode , retdata = pvc_ceph . ceph_util ( config )
2019-07-08 10:56:33 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_ceph . format_raw_output ( retdata )
2019-12-29 20:33:51 -05:00
cleanup ( retcode , retdata )
2018-10-27 18:11:58 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage osd
2018-10-27 18:11:58 -04:00
###############################################################################
@click.group ( name = ' osd ' , short_help = ' Manage OSDs in the PVC storage cluster. ' , context_settings = CONTEXT_SETTINGS )
def ceph_osd ( ) :
"""
Manage the Ceph OSDs of the PVC cluster .
"""
2020-01-02 11:19:11 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 11:19:11 -05:00
exit ( 1 )
2018-10-27 18:11:58 -04:00
2018-10-28 22:15:25 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage osd add
2018-10-28 22:15:25 -04:00
###############################################################################
@click.command ( name = ' add ' , short_help = ' Add new OSD. ' )
@click.argument (
' node '
)
@click.argument (
' device '
)
2018-11-01 23:03:27 -04:00
@click.option (
' -w ' , ' --weight ' , ' weight ' ,
default = 1.0 , show_default = True ,
help = ' Weight of the OSD within the CRUSH map. '
)
2019-06-21 15:52:28 -04:00
@click.option (
2020-01-08 10:34:27 -05:00
' -y ' , ' --yes ' , ' confirm_flag ' ,
2019-06-21 15:52:28 -04:00
is_flag = True , default = False ,
2020-01-08 10:34:27 -05:00
help = ' Confirm the removal '
2019-06-21 15:52:28 -04:00
)
2020-01-08 10:34:27 -05:00
def ceph_osd_add ( node , device , weight , confirm_flag ) :
2018-10-28 22:15:25 -04:00
"""
2020-01-04 14:06:36 -05:00
Add a new Ceph OSD on node NODE with block device DEVICE .
2018-10-28 22:15:25 -04:00
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Destroy all data and create a new OSD on {} : {} ' . format ( node , device ) , prompt_suffix = ' ? ' , abort = True )
except :
2019-06-21 15:52:28 -04:00
exit ( 0 )
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_osd_add ( config , node , device , weight )
cleanup ( retcode , retmsg )
2018-10-28 22:15:25 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage osd remove
2018-10-28 22:15:25 -04:00
###############################################################################
@click.command ( name = ' remove ' , short_help = ' Remove OSD. ' )
@click.argument (
' osdid '
)
2019-06-21 15:52:28 -04:00
@click.option (
2020-01-08 10:34:27 -05:00
' -y ' , ' --yes ' , ' confirm_flag ' ,
2019-06-21 15:52:28 -04:00
is_flag = True , default = False ,
2020-01-08 10:34:27 -05:00
help = ' Confirm the removal '
2019-06-21 15:52:28 -04:00
)
2020-01-08 10:34:27 -05:00
def ceph_osd_remove ( osdid , confirm_flag ) :
2018-10-28 22:15:25 -04:00
"""
2020-01-04 14:06:36 -05:00
Remove a Ceph OSD with ID OSDID .
2020-01-08 10:34:27 -05:00
DANGER : This will completely remove the OSD from the cluster . OSDs will rebalance which may negatively affect performance or available space .
2018-10-28 22:15:25 -04:00
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove OSD {} ' . format ( osdid ) , prompt_suffix = ' ? ' , abort = True )
except :
2019-06-21 15:52:28 -04:00
exit ( 0 )
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_osd_remove ( config , osdid )
cleanup ( retcode , retmsg )
2018-10-28 22:15:25 -04:00
2018-11-01 22:00:59 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage osd in
2018-11-01 22:00:59 -04:00
###############################################################################
@click.command ( name = ' in ' , short_help = ' Online OSD. ' )
@click.argument (
' osdid '
)
def ceph_osd_in ( osdid ) :
"""
2020-01-04 14:06:36 -05:00
Set a Ceph OSD with ID OSDID online .
2018-11-01 22:00:59 -04:00
"""
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_osd_state ( config , osdid , ' in ' )
cleanup ( retcode , retmsg )
2018-11-01 22:00:59 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage osd out
2018-11-01 22:00:59 -04:00
###############################################################################
@click.command ( name = ' out ' , short_help = ' Offline OSD. ' )
@click.argument (
' osdid '
)
def ceph_osd_out ( osdid ) :
"""
2020-01-04 14:06:36 -05:00
Set a Ceph OSD with ID OSDID offline .
2018-11-01 22:00:59 -04:00
"""
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_osd_state ( config , osdid , ' out ' )
cleanup ( retcode , retmsg )
2018-11-01 22:00:59 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage osd set
2018-11-01 22:00:59 -04:00
###############################################################################
@click.command ( name = ' set ' , short_help = ' Set property. ' )
@click.argument (
' osd_property '
)
def ceph_osd_set ( osd_property ) :
"""
Set a Ceph OSD property OSD_PROPERTY on the cluster .
Valid properties are :
full | pause | noup | nodown | noout | noin | nobackfill | norebalance | norecover | noscrub | nodeep - scrub | notieragent | sortbitwise | recovery_deletes | require_jewel_osds | require_kraken_osds
"""
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_osd_option ( config , osd_property , ' set ' )
cleanup ( retcode , retmsg )
2018-11-01 22:00:59 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage osd unset
2018-11-01 22:00:59 -04:00
###############################################################################
@click.command ( name = ' unset ' , short_help = ' Unset property. ' )
@click.argument (
' osd_property '
)
def ceph_osd_unset ( osd_property ) :
"""
Unset a Ceph OSD property OSD_PROPERTY on the cluster .
Valid properties are :
full | pause | noup | nodown | noout | noin | nobackfill | norebalance | norecover | noscrub | nodeep - scrub | notieragent | sortbitwise | recovery_deletes | require_jewel_osds | require_kraken_osds
"""
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_osd_option ( config , osd_property , ' unset ' )
cleanup ( retcode , retmsg )
2018-11-01 22:00:59 -04:00
2018-10-30 09:17:32 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage osd list
2018-10-30 09:17:32 -04:00
###############################################################################
@click.command ( name = ' list ' , short_help = ' List cluster OSDs. ' )
@click.argument (
' limit ' , default = None , required = False
)
def ceph_osd_list ( limit ) :
"""
2020-01-04 14:06:36 -05:00
List all Ceph OSDs ; optionally only match elements matching ID regex LIMIT .
2018-10-30 09:17:32 -04:00
"""
2019-12-29 20:33:51 -05:00
retcode , retdata = pvc_ceph . ceph_osd_list ( config , limit )
2019-07-05 00:29:47 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_ceph . format_list_osd ( retdata )
2019-12-29 20:33:51 -05:00
cleanup ( retcode , retdata )
2018-10-30 09:17:32 -04:00
2018-10-27 18:11:58 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage pool
2018-10-27 18:11:58 -04:00
###############################################################################
@click.group ( name = ' pool ' , short_help = ' Manage RBD pools in the PVC storage cluster. ' , context_settings = CONTEXT_SETTINGS )
def ceph_pool ( ) :
"""
Manage the Ceph RBD pools of the PVC cluster .
"""
2020-01-02 11:19:11 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 11:19:11 -05:00
exit ( 1 )
2018-09-21 23:43:30 -04:00
2018-10-31 23:38:17 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage pool add
2018-10-31 23:38:17 -04:00
###############################################################################
@click.command ( name = ' add ' , short_help = ' Add new RBD pool. ' )
@click.argument (
' name '
)
@click.argument (
' pgs '
)
2019-08-23 14:12:15 -04:00
@click.option (
' --replcfg ' , ' replcfg ' ,
default = ' copies=3,mincopies=2 ' , show_default = True , required = False ,
help = """
The replication configuration , specifying both a " copies " and " mincopies " value , separated by a
comma , e . g . " copies=3,mincopies=2 " . The " copies " value specifies the total number of replicas and should not exceed the total number of nodes ; the " mincopies " value specifies the minimum number of available copies to allow writes . For additional details please see the Cluster Architecture documentation .
"""
)
def ceph_pool_add ( name , pgs , replcfg ) :
2018-10-31 23:38:17 -04:00
"""
Add a new Ceph RBD pool with name NAME and PGS placement groups .
2019-08-23 14:12:15 -04:00
2018-10-31 23:38:17 -04:00
"""
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_pool_add ( config , name , pgs , replcfg )
cleanup ( retcode , retmsg )
2018-10-31 23:38:17 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage pool remove
2018-10-31 23:38:17 -04:00
###############################################################################
@click.command ( name = ' remove ' , short_help = ' Remove RBD pool. ' )
@click.argument (
' name '
)
2019-06-21 15:52:28 -04:00
@click.option (
2020-01-08 10:34:27 -05:00
' -y ' , ' --yes ' , ' confirm_flag ' ,
2019-06-21 15:52:28 -04:00
is_flag = True , default = False ,
2020-01-08 10:34:27 -05:00
help = ' Confirm the removal '
2019-06-21 15:52:28 -04:00
)
2020-01-08 10:34:27 -05:00
def ceph_pool_remove ( name , confirm_flag ) :
2018-10-31 23:38:17 -04:00
"""
Remove a Ceph RBD pool with name NAME and all volumes on it .
2020-01-08 10:34:27 -05:00
DANGER : This will completely remove the pool and all volumes contained in it from the cluster .
"""
if not confirm_flag :
try :
click . confirm ( ' Remove RBD pool {} ' . format ( name ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
2019-06-21 15:52:28 -04:00
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_pool_remove ( config , name )
cleanup ( retcode , retmsg )
2018-10-31 23:38:17 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage pool list
2018-10-31 23:38:17 -04:00
###############################################################################
@click.command ( name = ' list ' , short_help = ' List cluster RBD pools. ' )
@click.argument (
' limit ' , default = None , required = False
)
def ceph_pool_list ( limit ) :
"""
2020-01-04 14:06:36 -05:00
List all Ceph RBD pools ; optionally only match elements matching name regex LIMIT .
2018-10-31 23:38:17 -04:00
"""
2019-12-29 20:33:51 -05:00
retcode , retdata = pvc_ceph . ceph_pool_list ( config , limit )
2019-07-05 00:29:47 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_ceph . format_list_pool ( retdata )
2019-12-29 20:33:51 -05:00
cleanup ( retcode , retdata )
2018-10-31 23:38:17 -04:00
2019-06-19 00:23:14 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage volume
2019-06-19 00:23:14 -04:00
###############################################################################
@click.group ( name = ' volume ' , short_help = ' Manage RBD volumes in the PVC storage cluster. ' , context_settings = CONTEXT_SETTINGS )
def ceph_volume ( ) :
"""
Manage the Ceph RBD volumes of the PVC cluster .
"""
2020-01-02 11:19:11 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 11:19:11 -05:00
exit ( 1 )
2019-06-19 00:23:14 -04:00
2019-06-19 00:12:44 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage volume add
2019-06-19 00:12:44 -04:00
###############################################################################
@click.command ( name = ' add ' , short_help = ' Add new RBD volume. ' )
@click.argument (
' pool '
)
@click.argument (
' name '
)
@click.argument (
' size '
)
def ceph_volume_add ( pool , name , size ) :
"""
2019-06-21 09:22:24 -04:00
Add a new Ceph RBD volume with name NAME and size SIZE [ in human units , e . g . 1024 M , 20 G , etc . ] to pool POOL .
2019-06-19 00:12:44 -04:00
"""
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_volume_add ( config , pool , name , size )
cleanup ( retcode , retmsg )
2019-06-19 00:12:44 -04:00
2020-02-09 20:42:56 -05:00
###############################################################################
# pvc storage volume upload
###############################################################################
@click.command ( name = ' upload ' , short_help = ' Upload a local image file to RBD volume. ' )
@click.argument (
' pool '
)
@click.argument (
' name '
)
@click.argument (
' image_file '
)
@click.option (
' -f ' , ' --format ' , ' image_format ' ,
default = ' raw ' , show_default = True ,
help = ' The format of the source image. '
)
def ceph_volume_upload ( pool , name , image_format , image_file ) :
"""
Upload a disk image file IMAGE_FILE to the RBD volume NAME in pool POOL .
The volume NAME must exist in the pool before uploading to it , and must be large enough to fit the disk image in raw format .
If the image format is " raw " , the image is uploaded directly to the target volume without modification . Otherwise , it will be converted into raw format by " qemu-img convert " on the remote side before writing using a temporary volume . The image format must be a valid format recognized by " qemu-img " , such as " vmdk " or " qcow2 " .
"""
if not os . path . exists ( image_file ) :
click . echo ( " ERROR: File ' {} ' does not exist! " . format ( image_file ) )
exit ( 1 )
retcode , retmsg = pvc_ceph . ceph_volume_upload ( config , pool , name , image_format , image_file )
cleanup ( retcode , retmsg )
2019-06-19 00:12:44 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage volume remove
2019-06-19 00:12:44 -04:00
###############################################################################
@click.command ( name = ' remove ' , short_help = ' Remove RBD volume. ' )
@click.argument (
' pool '
)
@click.argument (
' name '
)
2019-06-21 15:52:28 -04:00
@click.option (
2020-01-08 10:34:27 -05:00
' -y ' , ' --yes ' , ' confirm_flag ' ,
2019-06-21 15:52:28 -04:00
is_flag = True , default = False ,
2020-01-08 10:34:27 -05:00
help = ' Confirm the removal '
2019-06-21 15:52:28 -04:00
)
2020-01-08 10:34:27 -05:00
def ceph_volume_remove ( pool , name , confirm_flag ) :
2019-06-19 00:12:44 -04:00
"""
Remove a Ceph RBD volume with name NAME from pool POOL .
2020-01-08 10:34:27 -05:00
DANGER : This will completely remove the volume and all data contained in it .
2019-06-19 00:12:44 -04:00
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove volume {} / {} ' . format ( pool , name ) , prompt_suffix = ' ? ' , abort = True )
except :
2019-06-21 15:52:28 -04:00
exit ( 0 )
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_volume_remove ( config , pool , name )
cleanup ( retcode , retmsg )
2019-06-19 00:12:44 -04:00
2019-07-26 14:24:22 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage volume resize
2019-07-26 14:24:22 -04:00
###############################################################################
@click.command ( name = ' resize ' , short_help = ' Resize RBD volume. ' )
@click.argument (
' pool '
)
@click.argument (
' name '
)
@click.argument (
' size '
)
def ceph_volume_resize ( pool , name , size ) :
"""
Resize an existing Ceph RBD volume with name NAME in pool POOL to size SIZE [ in human units , e . g . 1024 M , 20 G , etc . ] .
"""
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_volume_modify ( config , pool , name , new_size = size )
cleanup ( retcode , retmsg )
2019-07-26 14:24:22 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage volume rename
2019-07-26 14:24:22 -04:00
###############################################################################
2019-07-26 14:53:27 -04:00
@click.command ( name = ' rename ' , short_help = ' Rename RBD volume. ' )
2019-07-26 14:24:22 -04:00
@click.argument (
' pool '
)
@click.argument (
' name '
)
@click.argument (
' new_name '
)
def ceph_volume_rename ( pool , name , new_name ) :
"""
Rename an existing Ceph RBD volume with name NAME in pool POOL to name NEW_NAME .
"""
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_volume_modify ( config , pool , name , new_name = new_name )
cleanup ( retcode , retmsg )
2019-07-26 14:24:22 -04:00
2019-10-10 14:11:13 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage volume clone
2019-10-10 14:11:13 -04:00
###############################################################################
2019-12-29 20:33:51 -05:00
@click.command ( name = ' clone ' , short_help = ' Clone RBD volume. ' )
2019-10-10 14:11:13 -04:00
@click.argument (
' pool '
)
@click.argument (
' name '
)
@click.argument (
' new_name '
)
def ceph_volume_clone ( pool , name , new_name ) :
"""
Clone a Ceph RBD volume with name NAME in pool POOL to name NEW_NAME in pool POOL .
"""
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_volume_clone ( config , pool , name , new_name )
cleanup ( retcode , retmsg )
2019-10-10 14:11:13 -04:00
2019-06-19 00:12:44 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage volume list
2019-06-19 00:12:44 -04:00
###############################################################################
@click.command ( name = ' list ' , short_help = ' List cluster RBD volumes. ' )
@click.argument (
' limit ' , default = None , required = False
)
2019-06-19 14:11:03 -04:00
@click.option (
' -p ' , ' --pool ' , ' pool ' ,
2019-07-05 13:57:15 -04:00
default = None , show_default = True ,
2019-06-19 14:11:03 -04:00
help = ' Show volumes from this pool only. '
)
def ceph_volume_list ( limit , pool ) :
2019-06-19 00:12:44 -04:00
"""
2020-01-04 14:06:36 -05:00
List all Ceph RBD volumes ; optionally only match elements matching name regex LIMIT .
2019-06-19 00:12:44 -04:00
"""
2019-12-29 20:33:51 -05:00
retcode , retdata = pvc_ceph . ceph_volume_list ( config , limit , pool )
2019-07-05 00:29:47 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_ceph . format_list_volume ( retdata )
2019-12-29 20:33:51 -05:00
cleanup ( retcode , retdata )
2019-06-19 00:12:44 -04:00
2019-06-19 00:23:14 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage volume snapshot
2019-06-19 00:23:14 -04:00
###############################################################################
@click.group ( name = ' snapshot ' , short_help = ' Manage RBD volume snapshots in the PVC storage cluster. ' , context_settings = CONTEXT_SETTINGS )
def ceph_volume_snapshot ( ) :
"""
Manage the Ceph RBD volume snapshots of the PVC cluster .
"""
2020-01-02 11:19:11 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 11:19:11 -05:00
exit ( 1 )
2019-06-19 00:23:14 -04:00
2019-06-19 00:12:44 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage volume snapshot add
2019-06-19 00:12:44 -04:00
###############################################################################
@click.command ( name = ' add ' , short_help = ' Add new RBD volume snapshot. ' )
@click.argument (
' pool '
)
@click.argument (
' volume '
)
@click.argument (
' name '
)
2019-06-19 00:23:14 -04:00
def ceph_volume_snapshot_add ( pool , volume , name ) :
2019-06-19 00:12:44 -04:00
"""
2019-07-28 23:00:35 -04:00
Add a snapshot with name NAME of Ceph RBD volume VOLUME in pool POOL .
2019-06-19 00:12:44 -04:00
"""
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_snapshot_add ( config , pool , volume , name )
cleanup ( retcode , retmsg )
2019-06-19 00:12:44 -04:00
2019-07-28 23:00:35 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage volume snapshot rename
2019-07-28 23:00:35 -04:00
###############################################################################
@click.command ( name = ' rename ' , short_help = ' Rename RBD volume snapshot. ' )
@click.argument (
' pool '
)
@click.argument (
' volume '
)
@click.argument (
' name '
)
@click.argument (
' new_name '
)
def ceph_volume_snapshot_rename ( pool , volume , name , new_name ) :
"""
Rename an existing Ceph RBD volume snapshot with name NAME to name NEW_NAME for volume VOLUME in pool POOL .
"""
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_snapshot_modify ( config , pool , volume , name , new_name = new_name )
cleanup ( retcode , retmsg )
2019-07-28 23:00:35 -04:00
2019-06-19 00:12:44 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage volume snapshot remove
2019-06-19 00:12:44 -04:00
###############################################################################
@click.command ( name = ' remove ' , short_help = ' Remove RBD volume snapshot. ' )
@click.argument (
' pool '
)
@click.argument (
' volume '
)
@click.argument (
' name '
)
2019-06-21 15:52:28 -04:00
@click.option (
2020-01-08 10:34:27 -05:00
' -y ' , ' --yes ' , ' confirm_flag ' ,
2019-06-21 15:52:28 -04:00
is_flag = True , default = False ,
2020-01-08 10:34:27 -05:00
help = ' Confirm the removal '
2019-06-21 15:52:28 -04:00
)
2020-01-08 10:34:27 -05:00
def ceph_volume_snapshot_remove ( pool , volume , name , confirm_flag ) :
2019-06-19 00:12:44 -04:00
"""
2019-07-28 23:00:35 -04:00
Remove a Ceph RBD volume snapshot with name NAME from volume VOLUME in pool POOL .
2019-06-19 00:12:44 -04:00
2020-01-08 10:34:27 -05:00
DANGER : This will completely remove the snapshot .
"""
if not confirm_flag :
try :
click . confirm ( ' Remove snapshot {} for volume {} / {} ' . format ( name , pool , volume ) , prompt_suffix = ' ? ' , abort = True )
except :
2019-06-21 15:52:28 -04:00
exit ( 0 )
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_ceph . ceph_snapshot_remove ( config , pool , volume , name )
cleanup ( retcode , retmsg )
2019-06-19 00:12:44 -04:00
###############################################################################
2020-01-11 16:02:53 -05:00
# pvc storage volume snapshot list
2019-06-19 00:12:44 -04:00
###############################################################################
@click.command ( name = ' list ' , short_help = ' List cluster RBD volume shapshots. ' )
@click.argument (
2019-06-19 15:22:44 -04:00
' limit ' , default = None , required = False
2019-06-19 00:12:44 -04:00
)
2019-06-19 15:22:44 -04:00
@click.option (
' -p ' , ' --pool ' , ' pool ' ,
2019-07-05 13:57:15 -04:00
default = None , show_default = True ,
2019-06-19 15:22:44 -04:00
help = ' Show snapshots from this pool only. '
2019-06-19 00:12:44 -04:00
)
2019-06-19 15:22:44 -04:00
@click.option (
2020-01-04 11:58:30 -05:00
' -o ' , ' --volume ' , ' volume ' ,
2019-07-05 13:57:15 -04:00
default = None , show_default = True ,
2019-06-19 15:22:44 -04:00
help = ' Show snapshots from this volume only. '
2019-06-19 00:12:44 -04:00
)
2019-06-19 00:23:14 -04:00
def ceph_volume_snapshot_list ( pool , volume , limit ) :
2019-06-19 00:12:44 -04:00
"""
2019-06-19 15:22:44 -04:00
List all Ceph RBD volume snapshots ; optionally only match elements matching name regex LIMIT .
2019-06-19 00:12:44 -04:00
"""
2019-12-29 20:33:51 -05:00
retcode , retdata = pvc_ceph . ceph_snapshot_list ( config , limit , volume , pool )
2019-07-05 00:29:47 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_ceph . format_list_snapshot ( retdata )
2019-12-29 20:33:51 -05:00
cleanup ( retcode , retdata )
2019-06-19 00:12:44 -04:00
2020-01-02 11:18:46 -05:00
###############################################################################
# pvc provisioner
###############################################################################
@click.group ( name = ' provisioner ' , short_help = ' Manage PVC provisioner. ' , context_settings = CONTEXT_SETTINGS )
def cli_provisioner ( ) :
"""
Manage the PVC provisioner .
"""
2020-01-02 11:19:11 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 11:19:11 -05:00
exit ( 1 )
2020-01-02 11:18:46 -05:00
###############################################################################
# pvc provisioner template
###############################################################################
@click.group ( name = ' template ' , short_help = ' Manage PVC provisioner templates. ' , context_settings = CONTEXT_SETTINGS )
def provisioner_template ( ) :
"""
Manage the PVC provisioner template system .
"""
2020-01-02 11:19:11 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 11:19:11 -05:00
exit ( 1 )
2020-01-02 11:18:46 -05:00
###############################################################################
# pvc provisioner template list
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' list ' , short_help = ' List all templates. ' )
2020-01-02 11:18:46 -05:00
@click.argument (
' limit ' , default = None , required = False
)
2020-01-04 11:58:30 -05:00
def provisioner_template_list ( limit ) :
2020-01-02 11:18:46 -05:00
"""
List all templates in the PVC cluster provisioner .
"""
retcode , retdata = pvc_provisioner . template_list ( config , limit )
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_provisioner . format_list_template ( retdata )
2020-01-02 11:18:46 -05:00
cleanup ( retcode , retdata )
2020-01-04 11:58:30 -05:00
###############################################################################
# pvc provisioner template system
###############################################################################
@click.group ( name = ' system ' , short_help = ' Manage PVC provisioner system templates. ' , context_settings = CONTEXT_SETTINGS )
def provisioner_template_system ( ) :
"""
Manage the PVC provisioner system templates .
"""
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-04 11:58:30 -05:00
exit ( 1 )
###############################################################################
# pvc provisioner template system list
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' list ' , short_help = ' List all system templates. ' )
2020-01-04 11:58:30 -05:00
@click.argument (
' limit ' , default = None , required = False
)
def provisioner_template_system_list ( limit ) :
"""
List all system templates in the PVC cluster provisioner .
"""
retcode , retdata = pvc_provisioner . template_list ( config , limit , template_type = ' system ' )
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_provisioner . format_list_template ( retdata , template_type = ' system ' )
2020-01-04 11:58:30 -05:00
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner template system add
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' add ' , short_help = ' Add new system template. ' )
2020-01-04 11:58:30 -05:00
@click.argument (
' name '
)
@click.option (
' -u ' , ' --vcpus ' , ' vcpus ' ,
required = True , type = int ,
help = ' The number of vCPUs. '
)
@click.option (
' -m ' , ' --vram ' , ' vram ' ,
required = True , type = int ,
help = ' The amount of vRAM (in MB). '
)
@click.option (
' -s ' , ' --serial ' , ' serial ' ,
is_flag = True , default = False ,
help = ' Enable the virtual serial console. '
)
@click.option (
' -n ' , ' --vnc ' , ' vnc ' ,
is_flag = True , default = False ,
help = ' Enable the VNC console. '
)
@click.option (
' -b ' , ' --vnc-bind ' , ' vnc_bind ' ,
default = None ,
help = ' Bind VNC to this IP address instead of localhost. '
)
@click.option (
' --node-limit ' , ' node_limit ' ,
default = None ,
help = ' Limit VM operation to this CSV list of node(s). '
)
@click.option (
' --node-selector ' , ' node_selector ' ,
type = click . Choice ( [ ' mem ' , ' vcpus ' , ' vms ' , ' load ' ] , case_sensitive = False ) ,
default = None , # Use cluster default
help = ' Use this selector to determine the optimal node during migrations. '
)
@click.option (
' --node-autostart ' , ' node_autostart ' ,
is_flag = True , default = False ,
help = ' Autostart VM with their parent Node on first/next boot. '
)
def provisioner_template_system_add ( name , vcpus , vram , serial , vnc , vnc_bind , node_limit , node_selector , node_autostart ) :
"""
Add a new system template NAME to the PVC cluster provisioner .
"""
params = dict ( )
params [ ' name ' ] = name
params [ ' vcpus ' ] = vcpus
params [ ' vram ' ] = vram
params [ ' serial ' ] = serial
params [ ' vnc ' ] = vnc
if vnc :
params [ ' vnc_bind ' ] = vnc_bind
if node_limit :
params [ ' node_limit ' ] = node_limit
if node_selector :
params [ ' node_selector ' ] = node_selector
if node_autostart :
params [ ' node_autostart ' ] = node_autostart
retcode , retdata = pvc_provisioner . template_add ( config , params , template_type = ' system ' )
cleanup ( retcode , retdata )
2020-02-18 16:18:27 -05:00
###############################################################################
# pvc provisioner template system modify
###############################################################################
@click.command ( name = ' modify ' , short_help = ' Modify an existing system template. ' )
@click.argument (
' name '
)
@click.option (
' -u ' , ' --vcpus ' , ' vcpus ' ,
type = int ,
help = ' The number of vCPUs. '
)
@click.option (
' -m ' , ' --vram ' , ' vram ' ,
type = int ,
help = ' The amount of vRAM (in MB). '
)
@click.option (
' -s ' , ' --serial ' , ' serial ' ,
is_flag = True , default = None ,
help = ' Enable the virtual serial console. '
)
@click.option (
' -n ' , ' --vnc ' , ' vnc ' ,
is_flag = True , default = None ,
help = ' Enable the VNC console. '
)
@click.option (
' -b ' , ' --vnc-bind ' , ' vnc_bind ' ,
help = ' Bind VNC to this IP address instead of localhost. '
)
@click.option (
' --node-limit ' , ' node_limit ' ,
help = ' Limit VM operation to this CSV list of node(s). '
)
@click.option (
' --node-selector ' , ' node_selector ' ,
type = click . Choice ( [ ' mem ' , ' vcpus ' , ' vms ' , ' load ' ] , case_sensitive = False ) ,
help = ' Use this selector to determine the optimal node during migrations. '
)
@click.option (
' --node-autostart ' , ' node_autostart ' ,
is_flag = True , default = None ,
help = ' Autostart VM with their parent Node on first/next boot. '
)
def provisioner_template_system_modify ( name , vcpus , vram , serial , vnc , vnc_bind , node_limit , node_selector , node_autostart ) :
"""
Add a new system template NAME to the PVC cluster provisioner .
"""
params = dict ( )
params [ ' vcpus ' ] = vcpus
params [ ' vram ' ] = vram
params [ ' serial ' ] = serial
params [ ' vnc ' ] = vnc
params [ ' vnc_bind ' ] = vnc_bind
params [ ' node_limit ' ] = node_limit
params [ ' node_selector ' ] = node_selector
params [ ' node_autostart ' ] = node_autostart
retcode , retdata = pvc_provisioner . template_modify ( config , params , name , template_type = ' system ' )
cleanup ( retcode , retdata )
2020-01-04 11:58:30 -05:00
###############################################################################
# pvc provisioner template system remove
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' remove ' , short_help = ' Remove system template. ' )
2020-01-04 11:58:30 -05:00
@click.argument (
' name '
)
2020-01-08 10:34:27 -05:00
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def provisioner_template_system_remove ( name , confirm_flag ) :
2020-01-04 11:58:30 -05:00
"""
2020-01-04 13:04:01 -05:00
Remove system template NAME from the PVC cluster provisioner .
2020-01-04 11:58:30 -05:00
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove system template {} ' . format ( name ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
2020-01-04 11:58:30 -05:00
retcode , retdata = pvc_provisioner . template_remove ( config , name , template_type = ' system ' )
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner template network
###############################################################################
@click.group ( name = ' network ' , short_help = ' Manage PVC provisioner network templates. ' , context_settings = CONTEXT_SETTINGS )
def provisioner_template_network ( ) :
"""
Manage the PVC provisioner network templates .
"""
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-04 11:58:30 -05:00
exit ( 1 )
###############################################################################
# pvc provisioner template network list
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' list ' , short_help = ' List all network templates. ' )
2020-01-04 11:58:30 -05:00
@click.argument (
' limit ' , default = None , required = False
)
def provisioner_template_network_list ( limit ) :
"""
List all network templates in the PVC cluster provisioner .
"""
retcode , retdata = pvc_provisioner . template_list ( config , limit , template_type = ' network ' )
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_provisioner . format_list_template ( retdata , template_type = ' network ' )
2020-01-04 11:58:30 -05:00
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner template network add
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' add ' , short_help = ' Add new network template. ' )
2020-01-04 11:58:30 -05:00
@click.argument (
' name '
)
@click.option (
' -m ' , ' --mac-template ' , ' mac_template ' ,
default = None ,
help = ' Use this template for MAC addresses. '
)
def provisioner_template_network_add ( name , mac_template ) :
"""
Add a new network template to the PVC cluster provisioner .
MAC address templates are used to provide predictable MAC addresses for provisioned VMs .
The normal format of a MAC template is :
{ prefix } : XX : XX : { vmid } { netid }
The { prefix } variable is replaced by the provisioner with a standard prefix ( " 52:54:01 " ) ,
which is different from the randomly - generated MAC prefix ( " 52:54:00 " ) to avoid accidental
overlap of MAC addresses .
The { vmid } variable is replaced by a single hexidecimal digit representing the VM ' s ID,
the numerical suffix portion of its name ; VMs without a suffix numeral have ID 0. VMs with
IDs greater than 15 ( hexidecimal " f " ) will wrap back to 0.
The { netid } variable is replaced by the sequential identifier , starting at 0 , of the
network VNI of the interface ; for example , the first interface is 0 , the second is 1 , etc .
The four X digits are use - configurable . Use these digits to uniquely define the MAC
address .
Example : pvc provisioner template network add - - mac - template " {prefix} :2f:1f: {vmid} {netid} " test - template
The location of the two per - VM variables can be adjusted at the administrator ' s discretion,
or removed if not required ( e . g . a single - network template , or template for a single VM ) .
In such situations , be careful to avoid accidental overlap with other templates ' variable
portions .
"""
params = dict ( )
params [ ' name ' ] = name
params [ ' mac_template ' ] = mac_template
retcode , retdata = pvc_provisioner . template_add ( config , params , template_type = ' network ' )
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner template network remove
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' remove ' , short_help = ' Remove network template. ' )
2020-01-04 11:58:30 -05:00
@click.argument (
' name '
)
2020-01-08 10:34:27 -05:00
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def provisioner_template_network_remove ( name , confirm_flag ) :
2020-01-04 11:58:30 -05:00
"""
2020-01-04 13:04:01 -05:00
Remove network template MAME from the PVC cluster provisioner .
2020-01-04 11:58:30 -05:00
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove network template {} ' . format ( name ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
2020-01-04 11:58:30 -05:00
retcode , retdata = pvc_provisioner . template_remove ( config , name , template_type = ' network ' )
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner template network vni
###############################################################################
@click.group ( name = ' vni ' , short_help = ' Manage PVC provisioner network template VNIs. ' , context_settings = CONTEXT_SETTINGS )
def provisioner_template_network_vni ( ) :
"""
Manage the network VNIs in PVC provisioner network templates .
"""
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-04 11:58:30 -05:00
exit ( 1 )
###############################################################################
# pvc provisioner template network vni add
###############################################################################
@click.command ( name = ' add ' , short_help = ' Add network VNI to network template. ' )
@click.argument (
' name '
)
@click.argument (
' vni '
)
def provisioner_template_network_vni_add ( name , vni ) :
"""
Add a new network VNI to network template NAME .
2020-01-20 21:15:15 -05:00
Networks will be added to VMs in the order they are added and displayed within the template .
2020-01-04 11:58:30 -05:00
"""
params = dict ( )
retcode , retdata = pvc_provisioner . template_element_add ( config , name , vni , params , element_type = ' net ' , template_type = ' network ' )
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner template network vni remove
###############################################################################
@click.command ( name = ' remove ' , short_help = ' Remove network VNI from network template. ' )
@click.argument (
' name '
)
@click.argument (
' vni '
)
2020-01-08 10:34:27 -05:00
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def provisioner_template_network_vni_remove ( name , vni , confirm_flag ) :
2020-01-04 11:58:30 -05:00
"""
2020-01-04 13:04:01 -05:00
Remove network VNI from network template NAME .
2020-01-04 11:58:30 -05:00
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove VNI {} from network template {} ' . format ( vni , name ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
2020-01-04 11:58:30 -05:00
retcode , retdata = pvc_provisioner . template_element_remove ( config , name , vni , element_type = ' net ' , template_type = ' network ' )
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner template storage
###############################################################################
@click.group ( name = ' storage ' , short_help = ' Manage PVC provisioner storage templates. ' , context_settings = CONTEXT_SETTINGS )
def provisioner_template_storage ( ) :
"""
Manage the PVC provisioner storage templates .
"""
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-04 11:58:30 -05:00
exit ( 1 )
###############################################################################
# pvc provisioner template storage list
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' list ' , short_help = ' List all storage templates. ' )
2020-01-04 11:58:30 -05:00
@click.argument (
' limit ' , default = None , required = False
)
def provisioner_template_storage_list ( limit ) :
"""
List all storage templates in the PVC cluster provisioner .
"""
retcode , retdata = pvc_provisioner . template_list ( config , limit , template_type = ' storage ' )
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_provisioner . format_list_template ( retdata , template_type = ' storage ' )
2020-01-04 11:58:30 -05:00
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner template storage add
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' add ' , short_help = ' Add new storage template. ' )
2020-01-04 11:58:30 -05:00
@click.argument (
' name '
)
def provisioner_template_storage_add ( name ) :
"""
Add a new storage template to the PVC cluster provisioner .
"""
params = dict ( )
params [ ' name ' ] = name
retcode , retdata = pvc_provisioner . template_add ( config , params , template_type = ' storage ' )
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner template storage remove
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' remove ' , short_help = ' Remove storage template. ' )
2020-01-04 11:58:30 -05:00
@click.argument (
' name '
)
2020-01-08 10:34:27 -05:00
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def provisioner_template_storage_remove ( name , confirm_flag ) :
2020-01-04 11:58:30 -05:00
"""
2020-01-04 13:04:01 -05:00
Remove storage template NAME from the PVC cluster provisioner .
2020-01-04 11:58:30 -05:00
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove storage template {} ' . format ( name ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
2020-01-04 11:58:30 -05:00
retcode , retdata = pvc_provisioner . template_remove ( config , name , template_type = ' storage ' )
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner template storage disk
###############################################################################
@click.group ( name = ' disk ' , short_help = ' Manage PVC provisioner storage template disks. ' , context_settings = CONTEXT_SETTINGS )
def provisioner_template_storage_disk ( ) :
"""
Manage the disks in PVC provisioner storage templates .
"""
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-04 11:58:30 -05:00
exit ( 1 )
###############################################################################
# pvc provisioner template storage disk add
###############################################################################
@click.command ( name = ' add ' , short_help = ' Add disk to storage template. ' )
@click.argument (
' name '
)
@click.argument (
' disk '
)
@click.option (
' -p ' , ' --pool ' , ' pool ' ,
required = True ,
help = ' The storage pool for the disk. '
)
@click.option (
2020-01-05 19:11:52 -05:00
' -i ' , ' --source-volume ' , ' source_volume ' ,
default = None ,
help = ' The source volume to clone '
)
@click.option (
' -s ' , ' --size ' , ' size ' , type = int ,
default = None ,
2020-01-04 11:58:30 -05:00
help = ' The size of the disk (in GB). '
)
@click.option (
' -f ' , ' --filesystem ' , ' filesystem ' ,
default = None ,
help = ' The filesystem of the disk. '
)
@click.option (
' --fsarg ' , ' fsargs ' ,
default = None , multiple = True ,
help = ' Additional argument for filesystem creation, in arg=value format without leading dashes. '
)
@click.option (
' -m ' , ' --mountpoint ' , ' mountpoint ' ,
default = None ,
help = ' The target Linux mountpoint of the disk; requires a filesystem. '
)
2020-01-05 19:11:52 -05:00
def provisioner_template_storage_disk_add ( name , disk , pool , source_volume , size , filesystem , fsargs , mountpoint ) :
2020-01-04 11:58:30 -05:00
"""
Add a new DISK to storage template NAME .
2020-01-20 21:15:15 -05:00
DISK must be a Linux - style sdX / vdX disk identifier , such as " sda " or " vdb " . All disks in a template must use the same identifier format .
Disks will be added to VMs in sdX / vdX order . For disks with mountpoints , ensure this order is sensible .
2020-01-04 11:58:30 -05:00
"""
2020-01-05 19:11:52 -05:00
if source_volume and ( size or filesystem or mountpoint ) :
click . echo ( ' The " --source-volume " option is not compatible with the " --size " , " --filesystem " , or " --mountpoint " options. ' )
exit ( 1 )
2020-01-04 11:58:30 -05:00
params = dict ( )
params [ ' pool ' ] = pool
2020-01-05 19:11:52 -05:00
params [ ' source_volume ' ] = source_volume
2020-01-04 11:58:30 -05:00
params [ ' disk_size ' ] = size
if filesystem :
params [ ' filesystem ' ] = filesystem
if filesystem and fsargs :
dash_fsargs = list ( )
for arg in fsargs :
arg_len = len ( arg . split ( ' = ' ) [ 0 ] )
if arg_len == 1 :
dash_fsargs . append ( ' - ' + arg )
else :
dash_fsargs . append ( ' -- ' + arg )
params [ ' filesystem_arg ' ] = dash_fsargs
if filesystem and mountpoint :
params [ ' mountpoint ' ] = mountpoint
retcode , retdata = pvc_provisioner . template_element_add ( config , name , disk , params , element_type = ' disk ' , template_type = ' storage ' )
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner template storage disk remove
###############################################################################
@click.command ( name = ' remove ' , short_help = ' Remove disk from storage template. ' )
@click.argument (
' name '
)
@click.argument (
' disk '
)
2020-01-08 10:34:27 -05:00
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def provisioner_template_storage_disk_remove ( name , disk , confirm_flag ) :
2020-01-04 11:58:30 -05:00
"""
2020-01-04 13:04:01 -05:00
Remove DISK from storage template NAME .
DISK must be a Linux - style disk identifier such as " sda " or " vdb " .
2020-01-04 11:58:30 -05:00
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove disk {} from storage template {} ' . format ( disk , name ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
2020-01-04 11:58:30 -05:00
retcode , retdata = pvc_provisioner . template_element_remove ( config , name , disk , element_type = ' disk ' , template_type = ' storage ' )
cleanup ( retcode , retdata )
2020-01-04 13:04:01 -05:00
###############################################################################
# pvc provisioner userdata
###############################################################################
@click.group ( name = ' userdata ' , short_help = ' Manage PVC provisioner userdata documents. ' , context_settings = CONTEXT_SETTINGS )
def provisioner_userdata ( ) :
"""
Manage userdata documents in the PVC provisioner .
"""
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-04 13:04:01 -05:00
exit ( 1 )
###############################################################################
# pvc provisioner userdata list
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' list ' , short_help = ' List all userdata documents. ' )
2020-01-04 13:04:01 -05:00
@click.argument (
' limit ' , default = None , required = False
)
@click.option (
' -f ' , ' --full ' , ' full ' ,
is_flag = True , default = False ,
help = ' Show all lines of the document instead of first 4. '
)
def provisioner_userdata_list ( limit , full ) :
"""
List all userdata documents in the PVC cluster provisioner .
"""
retcode , retdata = pvc_provisioner . userdata_list ( config , limit )
if retcode :
if not full :
lines = 4
else :
lines = None
2020-01-05 12:35:00 -05:00
retdata = pvc_provisioner . format_list_userdata ( retdata , lines )
2020-01-04 13:04:01 -05:00
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner userdata add
###############################################################################
@click.command ( name = ' add ' , short_help = ' Define userdata document from file. ' )
@click.argument (
' name '
)
@click.argument (
' filename ' , type = click . File ( )
)
def provisioner_userdata_add ( name , filename ) :
"""
Add a new userdata document NAME from file FILENAME .
"""
# Open the XML file
userdata = filename . read ( )
filename . close ( )
params = dict ( )
params [ ' name ' ] = name
2020-01-06 23:52:29 -05:00
params [ ' data ' ] = userdata . strip ( )
2020-01-04 13:04:01 -05:00
retcode , retmsg = pvc_provisioner . userdata_add ( config , params )
cleanup ( retcode , retmsg )
###############################################################################
# pvc provisioner userdata modify
###############################################################################
@click.command ( name = ' modify ' , short_help = ' Modify existing userdata document. ' )
@click.option (
' -e ' , ' --editor ' , ' editor ' , is_flag = True ,
help = ' Use local editor to modify existing document. '
)
@click.argument (
' name '
)
@click.argument (
' filename ' , type = click . File ( ) , default = None , required = False
)
def provisioner_userdata_modify ( name , filename , editor ) :
"""
Modify existing userdata document NAME , either in - editor or with replacement FILE .
"""
if editor == False and filename == None :
cleanup ( False , ' Either a file or the " --editor " option must be specified. ' )
if editor == True :
# Grab the current config
retcode , retdata = pvc_provisioner . userdata_info ( config , name )
if not retcode :
click . echo ( retdata )
exit ( 1 )
2020-01-08 09:33:01 -05:00
current_userdata = retdata [ ' userdata ' ] . strip ( )
2020-01-04 13:04:01 -05:00
2020-01-08 09:33:01 -05:00
new_userdata = click . edit ( text = current_userdata , require_save = True , extension = ' .yaml ' )
if new_userdata is None :
2020-01-04 13:04:01 -05:00
click . echo ( ' Aborting with no modifications. ' )
exit ( 0 )
2020-01-08 09:33:01 -05:00
else :
new_userdata = new_userdata . strip ( )
2020-01-04 13:04:01 -05:00
2020-01-08 09:33:01 -05:00
# Show a diff and confirm
2020-01-04 13:04:01 -05:00
click . echo ( ' Pending modifications: ' )
click . echo ( ' ' )
2020-01-08 09:33:01 -05:00
diff = list ( difflib . unified_diff ( current_userdata . split ( ' \n ' ) , new_userdata . split ( ' \n ' ) , fromfile = ' current ' , tofile = ' modified ' , fromfiledate = ' ' , tofiledate = ' ' , n = 3 , lineterm = ' ' ) )
2020-01-04 13:04:01 -05:00
for line in diff :
if re . match ( ' ^ \ + ' , line ) != None :
click . echo ( colorama . Fore . GREEN + line + colorama . Fore . RESET )
elif re . match ( ' ^ \ - ' , line ) != None :
click . echo ( colorama . Fore . RED + line + colorama . Fore . RESET )
elif re . match ( ' ^ \ ^ ' , line ) != None :
click . echo ( colorama . Fore . BLUE + line + colorama . Fore . RESET )
else :
click . echo ( line )
click . echo ( ' ' )
click . confirm ( ' Write modifications to cluster? ' , abort = True )
userdata = new_userdata
# We're operating in replace mode
else :
# Open the new file
userdata = filename . read ( ) . strip ( )
filename . close ( )
params = dict ( )
params [ ' data ' ] = userdata
retcode , retmsg = pvc_provisioner . userdata_modify ( config , name , params )
cleanup ( retcode , retmsg )
###############################################################################
# pvc provisioner userdata remove
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' remove ' , short_help = ' Remove userdata document. ' )
2020-01-04 13:04:01 -05:00
@click.argument (
' name '
)
2020-01-08 10:34:27 -05:00
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def provisioner_userdata_remove ( name , confirm_flag ) :
2020-01-04 13:04:01 -05:00
"""
Remove userdata document NAME from the PVC cluster provisioner .
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove userdata document {} ' . format ( name ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
2020-01-04 13:04:01 -05:00
retcode , retdata = pvc_provisioner . userdata_remove ( config , name )
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner script
###############################################################################
@click.group ( name = ' script ' , short_help = ' Manage PVC provisioner scripts. ' , context_settings = CONTEXT_SETTINGS )
def provisioner_script ( ) :
"""
Manage scripts in the PVC provisioner .
"""
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-04 13:04:01 -05:00
exit ( 1 )
###############################################################################
# pvc provisioner script list
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' list ' , short_help = ' List all scripts. ' )
2020-01-04 13:04:01 -05:00
@click.argument (
' limit ' , default = None , required = False
)
@click.option (
' -f ' , ' --full ' , ' full ' ,
is_flag = True , default = False ,
help = ' Show all lines of the document instead of first 4. '
)
def provisioner_script_list ( limit , full ) :
"""
List all scripts in the PVC cluster provisioner .
"""
retcode , retdata = pvc_provisioner . script_list ( config , limit )
if retcode :
if not full :
lines = 4
else :
lines = None
2020-01-05 12:35:00 -05:00
retdata = pvc_provisioner . format_list_script ( retdata , lines )
2020-01-04 13:04:01 -05:00
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner script add
###############################################################################
@click.command ( name = ' add ' , short_help = ' Define script from file. ' )
@click.argument (
' name '
)
@click.argument (
' filename ' , type = click . File ( )
)
def provisioner_script_add ( name , filename ) :
"""
Add a new script NAME from file FILENAME .
"""
# Open the XML file
script = filename . read ( )
filename . close ( )
params = dict ( )
params [ ' name ' ] = name
2020-01-06 23:52:29 -05:00
params [ ' data ' ] = script . strip ( )
2020-01-04 13:04:01 -05:00
retcode , retmsg = pvc_provisioner . script_add ( config , params )
cleanup ( retcode , retmsg )
###############################################################################
# pvc provisioner script modify
###############################################################################
@click.command ( name = ' modify ' , short_help = ' Modify existing script. ' )
@click.option (
' -e ' , ' --editor ' , ' editor ' , is_flag = True ,
help = ' Use local editor to modify existing document. '
)
@click.argument (
' name '
)
@click.argument (
' filename ' , type = click . File ( ) , default = None , required = False
)
def provisioner_script_modify ( name , filename , editor ) :
"""
Modify existing script NAME , either in - editor or with replacement FILE .
"""
if editor == False and filename == None :
cleanup ( False , ' Either a file or the " --editor " option must be specified. ' )
if editor == True :
# Grab the current config
retcode , retdata = pvc_provisioner . script_info ( config , name )
if not retcode :
click . echo ( retdata )
exit ( 1 )
2020-01-08 09:33:01 -05:00
current_script = retdata [ ' script ' ] . strip ( )
2020-01-04 13:04:01 -05:00
2020-01-08 09:33:01 -05:00
new_script = click . edit ( text = current_script , require_save = True , extension = ' .py ' )
if new_script is None :
2020-01-04 13:04:01 -05:00
click . echo ( ' Aborting with no modifications. ' )
exit ( 0 )
2020-01-08 09:33:01 -05:00
else :
new_script = new_script . strip ( )
2020-01-04 13:04:01 -05:00
2020-01-08 09:33:01 -05:00
# Show a diff and confirm
2020-01-04 13:04:01 -05:00
click . echo ( ' Pending modifications: ' )
click . echo ( ' ' )
2020-01-08 09:33:01 -05:00
diff = list ( difflib . unified_diff ( current_script . split ( ' \n ' ) , new_script . split ( ' \n ' ) , fromfile = ' current ' , tofile = ' modified ' , fromfiledate = ' ' , tofiledate = ' ' , n = 3 , lineterm = ' ' ) )
2020-01-04 13:04:01 -05:00
for line in diff :
if re . match ( ' ^ \ + ' , line ) != None :
click . echo ( colorama . Fore . GREEN + line + colorama . Fore . RESET )
elif re . match ( ' ^ \ - ' , line ) != None :
click . echo ( colorama . Fore . RED + line + colorama . Fore . RESET )
elif re . match ( ' ^ \ ^ ' , line ) != None :
click . echo ( colorama . Fore . BLUE + line + colorama . Fore . RESET )
else :
click . echo ( line )
click . echo ( ' ' )
click . confirm ( ' Write modifications to cluster? ' , abort = True )
script = new_script
# We're operating in replace mode
else :
# Open the new file
script = filename . read ( ) . strip ( )
filename . close ( )
params = dict ( )
params [ ' data ' ] = script
retcode , retmsg = pvc_provisioner . script_modify ( config , name , params )
cleanup ( retcode , retmsg )
###############################################################################
# pvc provisioner script remove
###############################################################################
2020-01-04 14:06:36 -05:00
@click.command ( name = ' remove ' , short_help = ' Remove script. ' )
2020-01-04 13:04:01 -05:00
@click.argument (
' name '
)
2020-01-08 10:34:27 -05:00
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def provisioner_script_remove ( name , confirm_flag ) :
2020-01-04 13:04:01 -05:00
"""
Remove script NAME from the PVC cluster provisioner .
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove provisioning script {} ' . format ( name ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
params = dict ( )
2020-01-04 13:04:01 -05:00
retcode , retdata = pvc_provisioner . script_remove ( config , name )
cleanup ( retcode , retdata )
2020-01-04 11:58:30 -05:00
2020-01-02 11:18:46 -05:00
2020-02-17 22:52:49 -05:00
###############################################################################
# pvc provisioner ova
###############################################################################
@click.group ( name = ' ova ' , short_help = ' Manage PVC provisioner OVA images. ' , context_settings = CONTEXT_SETTINGS )
def provisioner_ova ( ) :
"""
Manage ovas in the PVC provisioner .
"""
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
exit ( 1 )
###############################################################################
# pvc provisioner ova list
###############################################################################
@click.command ( name = ' list ' , short_help = ' List all OVA images. ' )
@click.argument (
' limit ' , default = None , required = False
)
def provisioner_ova_list ( limit ) :
"""
List all OVA images in the PVC cluster provisioner .
"""
retcode , retdata = pvc_provisioner . ova_list ( config , limit )
if retcode :
retdata = pvc_provisioner . format_list_ova ( retdata )
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner ova upload
###############################################################################
@click.command ( name = ' upload ' , short_help = ' Upload OVA file. ' )
@click.argument (
' name '
)
@click.argument (
' filename '
)
@click.option (
' -p ' , ' --pool ' , ' pool ' ,
required = True ,
help = ' The storage pool for the OVA images. '
)
def provisioner_ova_upload ( name , filename , pool ) :
"""
Upload a new OVA image NAME from FILENAME .
Only single - file ( . ova ) OVA / OVF images are supported . For multi - file ( . ovf + . vmdk ) OVF images , concatenate them with " tar " then upload the resulting file .
Once uploaded , a provisioner system template and OVA - type profile , each named NAME , will be created to store the configuration of the OVA .
Note that the provisioner profile for the OVA will not contain any network template definitions , and will ignore network definitions from the OVA itself . The administrator must modify the profile ' s network template as appropriate to set the desired network configuration.
Storage templates , provisioning scripts , and arguments for OVA - type profiles will be ignored and should not be set .
"""
if not os . path . exists ( filename ) :
click . echo ( " ERROR: File ' {} ' does not exist! " . format ( filename ) )
exit ( 1 )
params = dict ( )
params [ ' pool ' ] = pool
params [ ' ova_size ' ] = os . path . getsize ( filename )
retcode , retdata = pvc_provisioner . ova_upload ( config , name , filename , params )
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner ova remove
###############################################################################
@click.command ( name = ' remove ' , short_help = ' Remove OVA image. ' )
@click.argument (
' name '
)
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def provisioner_ova_remove ( name , confirm_flag ) :
"""
Remove OVA image NAME from the PVC cluster provisioner .
"""
if not confirm_flag :
try :
2020-02-17 23:31:03 -05:00
click . confirm ( ' Remove OVA image {} ' . format ( name ) , prompt_suffix = ' ? ' , abort = True )
2020-02-17 22:52:49 -05:00
except :
exit ( 0 )
retcode , retdata = pvc_provisioner . ova_remove ( config , name )
cleanup ( retcode , retdata )
2020-01-04 14:06:36 -05:00
###############################################################################
# pvc provisioner profile
###############################################################################
@click.group ( name = ' profile ' , short_help = ' Manage PVC provisioner profiless. ' , context_settings = CONTEXT_SETTINGS )
def provisioner_profile ( ) :
"""
Manage profiles in the PVC provisioner .
"""
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-04 14:06:36 -05:00
exit ( 1 )
###############################################################################
# pvc provisioner profile list
###############################################################################
@click.command ( name = ' list ' , short_help = ' List all profiles. ' )
@click.argument (
' limit ' , default = None , required = False
)
def provisioner_profile_list ( limit ) :
"""
List all profiles in the PVC cluster provisioner .
"""
retcode , retdata = pvc_provisioner . profile_list ( config , limit )
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_provisioner . format_list_profile ( retdata )
2020-01-04 14:06:36 -05:00
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner profile add
###############################################################################
@click.command ( name = ' add ' , short_help = ' Add provisioner profile. ' )
@click.argument (
' name '
)
2020-02-17 22:52:49 -05:00
@click.option (
' -p ' , ' --profile-type ' , ' profile_type ' ,
default = ' provisioner ' , show_default = True ,
type = click . Choice ( [ ' provisioner ' , ' ova ' ] , case_sensitive = False ) ,
help = ' The type of profile. '
)
2020-01-04 14:06:36 -05:00
@click.option (
' -s ' , ' --system-template ' , ' system_template ' ,
help = ' The system template for the profile. '
)
@click.option (
' -n ' , ' --network-template ' , ' network_template ' ,
help = ' The network template for the profile. '
)
@click.option (
' -t ' , ' --storage-template ' , ' storage_template ' ,
help = ' The storage template for the profile. '
)
@click.option (
' -u ' , ' --userdata ' , ' userdata ' ,
help = ' The userdata document for the profile. '
)
@click.option (
' -x ' , ' --script ' , ' script ' ,
help = ' The script for the profile. '
)
2020-02-17 22:52:49 -05:00
@click.option (
' -o ' , ' --ova ' , ' ova ' ,
help = ' The OVA image for the profile. '
)
2020-01-04 14:06:36 -05:00
@click.option (
' -a ' , ' --script-arg ' , ' script_args ' ,
default = [ ] , multiple = True ,
help = ' Additional argument to the script install() function in key=value format. '
)
2020-02-17 22:52:49 -05:00
def provisioner_profile_add ( name , profile_type , system_template , network_template , storage_template , userdata , script , ova , script_args ) :
2020-01-04 14:06:36 -05:00
"""
Add a new provisioner profile NAME .
"""
params = dict ( )
params [ ' name ' ] = name
2020-02-17 22:52:49 -05:00
params [ ' profile_type ' ] = profile_type
2020-01-04 14:06:36 -05:00
params [ ' system_template ' ] = system_template
params [ ' network_template ' ] = network_template
params [ ' storage_template ' ] = storage_template
params [ ' userdata ' ] = userdata
params [ ' script ' ] = script
2020-02-17 22:52:49 -05:00
params [ ' ova ' ] = ova
2020-01-04 14:06:36 -05:00
params [ ' arg ' ] = script_args
retcode , retdata = pvc_provisioner . profile_add ( config , params )
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner profile modify
###############################################################################
@click.command ( name = ' modify ' , short_help = ' Modify provisioner profile. ' )
@click.argument (
' name '
)
@click.option (
' -s ' , ' --system-template ' , ' system_template ' ,
default = None ,
help = ' The system template for the profile. '
)
@click.option (
' -n ' , ' --network-template ' , ' network_template ' ,
default = None ,
help = ' The network template for the profile. '
)
@click.option (
' -t ' , ' --storage-template ' , ' storage_template ' ,
default = None ,
help = ' The storage template for the profile. '
)
@click.option (
' -u ' , ' --userdata ' , ' userdata ' ,
default = None ,
help = ' The userdata document for the profile. '
)
@click.option (
' -x ' , ' --script ' , ' script ' ,
default = None ,
help = ' The script for the profile. '
)
@click.option (
' -d ' , ' --delete-script-args ' , ' delete_script_args ' ,
default = False , is_flag = True ,
help = " Delete any existing script arguments. "
)
@click.option (
' -a ' , ' --script-arg ' , ' script_args ' ,
default = None , multiple = True ,
help = ' Additional argument to the script install() function in key=value format. '
)
def provisioner_profile_modify ( name , system_template , network_template , storage_template , userdata , script , delete_script_args , script_args ) :
"""
Modify existing provisioner profile NAME .
"""
params = dict ( )
if system_template is not None :
params [ ' system_template ' ] = system_template
if network_template is not None :
params [ ' network_template ' ] = network_template
if storage_template is not None :
params [ ' storage_template ' ] = storage_template
if userdata is not None :
params [ ' userdata ' ] = userdata
if script is not None :
params [ ' script ' ] = script
if delete_script_args :
params [ ' arg ' ] = [ ]
if script_args is not None :
params [ ' arg ' ] = script_args
retcode , retdata = pvc_provisioner . profile_modify ( config , name , params )
cleanup ( retcode , retdata )
###############################################################################
# pvc provisioner profile remove
###############################################################################
@click.command ( name = ' remove ' , short_help = ' Remove profile. ' )
@click.argument (
' name '
)
2020-01-08 10:34:27 -05:00
@click.option (
' -y ' , ' --yes ' , ' confirm_flag ' ,
is_flag = True , default = False ,
help = ' Confirm the removal '
)
def provisioner_profile_remove ( name , confirm_flag ) :
2020-01-04 14:06:36 -05:00
"""
Remove profile NAME from the PVC cluster provisioner .
"""
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove profile {} ' . format ( name ) , prompt_suffix = ' ? ' , abort = True )
except :
exit ( 0 )
2020-01-04 14:06:36 -05:00
retcode , retdata = pvc_provisioner . profile_remove ( config , name )
cleanup ( retcode , retdata )
2020-01-02 11:18:46 -05:00
2020-01-04 14:31:22 -05:00
###############################################################################
# pvc provisioner create
###############################################################################
@click.command ( name = ' create ' , short_help = ' Create new VM. ' )
@click.argument (
' name '
)
@click.argument (
' profile '
)
2020-01-08 20:13:26 -05:00
@click.option (
' -d/-D ' , ' --define/--no-define ' , ' define_flag ' ,
is_flag = True , default = True , show_default = True ,
help = ' Define the VM automatically during provisioning. '
2020-01-09 09:46:58 -05:00
)
2020-01-08 20:13:26 -05:00
@click.option (
' -s/-S ' , ' --start/--no-start ' , ' start_flag ' ,
is_flag = True , default = True , show_default = True ,
help = ' Start the VM automatically upon completion of provisioning. '
2020-01-09 09:46:58 -05:00
)
2020-01-08 14:41:19 -05:00
@click.option (
' -w ' , ' --wait ' , ' wait_flag ' ,
is_flag = True , default = False ,
help = ' Wait for provisioning to complete, showing progress '
)
2020-01-08 20:13:26 -05:00
def provisioner_create ( name , profile , wait_flag , define_flag , start_flag ) :
2020-01-04 14:31:22 -05:00
"""
Create a new VM NAME with profile PROFILE .
2020-01-08 20:13:26 -05:00
The " --no-start " flag can be used to prevent automatic startup of the VM once provisioning
is completed . This can be useful for the administrator to preform additional actions to
the VM after provisioning is completed . Note that the VM will remain in " provision " state
until its state is explicitly changed ( e . g . with " pvc vm start " ) .
The " --no-define " flag implies " --no-start " , and can be used to prevent definition of the
created VM on the PVC cluster . This can be useful for the administrator to create a " template "
set of VM disks via the normal provisioner , but without ever starting the resulting VM . The
resulting disk ( s ) can then be used as source volumes in other disk templates .
2020-01-04 14:31:22 -05:00
"""
2020-01-08 20:13:26 -05:00
if not define_flag :
start_flag = False
retcode , retdata = pvc_provisioner . vm_create ( config , name , profile , wait_flag , define_flag , start_flag )
2020-01-08 14:41:19 -05:00
if retcode and wait_flag :
task_id = retdata
click . echo ( " Task ID: {} " . format ( task_id ) )
click . echo ( )
# Wait for the task to start
click . echo ( " Waiting for task to start... " , nl = False )
while True :
time . sleep ( 1 )
task_status = pvc_provisioner . task_status ( config , task_id , is_watching = True )
if task_status . get ( ' state ' ) != ' PENDING ' :
break
click . echo ( " . " , nl = False )
click . echo ( " done. " )
click . echo ( )
# Start following the task state, updating progress as we go
2020-01-08 17:07:57 -05:00
total_task = task_status . get ( ' total ' )
with click . progressbar ( length = total_task , show_eta = False ) as bar :
2020-01-08 14:41:19 -05:00
last_task = 0
maxlen = 0
while True :
time . sleep ( 1 )
if task_status . get ( ' state ' ) != ' RUNNING ' :
break
if task_status . get ( ' current ' ) > last_task :
current_task = int ( task_status . get ( ' current ' ) )
bar . update ( current_task - last_task )
last_task = current_task
# The extensive spaces at the end cause this to overwrite longer previous messages
curlen = len ( str ( task_status . get ( ' status ' ) ) )
if curlen > maxlen :
maxlen = curlen
lendiff = maxlen - curlen
overwrite_whitespace = " " * lendiff
click . echo ( " " + task_status . get ( ' status ' ) + overwrite_whitespace , nl = False )
task_status = pvc_provisioner . task_status ( config , task_id , is_watching = True )
2020-01-08 17:07:57 -05:00
if task_status . get ( ' state ' ) == ' SUCCESS ' :
bar . update ( total_task - last_task )
2020-01-08 14:41:19 -05:00
click . echo ( )
retdata = task_status . get ( ' state ' ) + " : " + task_status . get ( ' status ' )
2020-01-04 14:31:22 -05:00
2020-01-08 14:41:19 -05:00
cleanup ( retcode , retdata )
2020-01-04 14:31:22 -05:00
###############################################################################
# pvc provisioner status
###############################################################################
@click.command ( name = ' status ' , short_help = ' Show status of provisioner job. ' )
@click.argument (
2020-01-12 14:01:47 -05:00
' job ' , required = False , default = None
2020-01-04 14:31:22 -05:00
)
def provisioner_status ( job ) :
"""
2020-01-12 14:01:47 -05:00
Show status of provisioner job JOB or a list of jobs .
2020-01-04 14:31:22 -05:00
"""
retcode , retdata = pvc_provisioner . task_status ( config , job )
2020-01-12 14:23:11 -05:00
if job is None and retcode :
retdata = pvc_provisioner . format_list_task ( retdata )
2020-01-04 14:31:22 -05:00
cleanup ( retcode , retdata )
2020-01-02 11:18:46 -05:00
2020-01-09 10:53:27 -05:00
###############################################################################
# pvc maintenance
###############################################################################
@click.group ( name = ' maintenance ' , short_help = ' Manage PVC cluster maintenance state. ' , context_settings = CONTEXT_SETTINGS )
def cli_maintenance ( ) :
"""
Manage the maintenance mode of the PVC cluster .
"""
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-09 10:53:27 -05:00
exit ( 1 )
2020-01-02 11:18:46 -05:00
2020-01-09 10:53:27 -05:00
###############################################################################
# pvc maintenance on
###############################################################################
@click.command ( name = ' on ' , short_help = ' Enable cluster maintenance mode. ' )
def maintenance_on ( ) :
"""
Enable maintenance mode on the PVC cluster .
"""
retcode , retdata = pvc_cluster . maintenance_mode ( config , ' true ' )
cleanup ( retcode , retdata )
2020-01-02 11:18:46 -05:00
2020-01-09 10:53:27 -05:00
###############################################################################
# pvc maintenance off
###############################################################################
@click.command ( name = ' off ' , short_help = ' Disable cluster maintenance mode. ' )
def maintenance_off ( ) :
"""
Disable maintenance mode on the PVC cluster .
"""
retcode , retdata = pvc_cluster . maintenance_mode ( config , ' false ' )
cleanup ( retcode , retdata )
2020-01-02 11:18:46 -05:00
2019-08-25 21:18:33 -04:00
###############################################################################
2019-10-22 11:23:12 -04:00
# pvc status
2019-08-25 21:18:33 -04:00
###############################################################################
2019-10-22 11:23:12 -04:00
@click.command ( name = ' status ' , short_help = ' Show current cluster status. ' )
@click.option (
' -f ' , ' --format ' , ' oformat ' , default = ' plain ' , show_default = True ,
type = click . Choice ( [ ' plain ' , ' json ' , ' json-pretty ' ] ) ,
help = ' Output format of cluster status information. '
)
def status_cluster ( oformat ) :
"""
Show basic information and health for the active PVC cluster .
"""
2020-01-02 12:18:41 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 12:18:41 -05:00
exit ( 1 )
2019-12-29 20:33:51 -05:00
retcode , retdata = pvc_cluster . get_info ( config )
2019-10-22 11:23:12 -04:00
if retcode :
2020-01-05 12:35:00 -05:00
retdata = pvc_cluster . format_info ( retdata , oformat )
2019-12-29 20:33:51 -05:00
cleanup ( retcode , retdata )
2019-10-10 14:09:40 -04:00
2019-10-22 11:23:12 -04:00
###############################################################################
# pvc init
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' init ' , short_help = ' Initialize a new cluster. ' )
2019-06-21 14:16:32 -04:00
@click.option (
2020-01-08 10:34:27 -05:00
' -y ' , ' --yes ' , ' confirm_flag ' ,
2019-06-21 15:09:15 -04:00
is_flag = True , default = False ,
2020-01-08 10:34:27 -05:00
help = ' Confirm the removal '
2019-06-21 14:16:32 -04:00
)
2020-01-08 10:34:27 -05:00
def init_cluster ( confirm_flag ) :
2018-06-06 01:07:59 -04:00
"""
2019-03-10 20:40:03 -04:00
Perform initialization of a new PVC cluster .
2018-06-06 01:07:59 -04:00
"""
2020-01-02 12:18:41 -05:00
# Abort commands under this group if config is bad
if config . get ( ' badcfg ' , None ) :
2020-02-08 19:16:19 -05:00
click . echo ( ' No cluster specified and no local pvcapid.yaml configuration found. Use " pvc cluster " to add a cluster API to connect to. ' )
2020-01-02 12:18:41 -05:00
exit ( 1 )
2018-06-06 01:07:59 -04:00
2020-01-08 10:34:27 -05:00
if not confirm_flag :
try :
click . confirm ( ' Remove all existing cluster data from coordinators and initialize a new cluster ' . format ( name ) , prompt_suffix = ' ? ' , abort = True )
except :
2019-06-21 15:38:32 -04:00
exit ( 0 )
2019-08-20 09:19:56 -04:00
# Easter-egg
click . echo ( " Some music while we ' re Layin ' Pipe? https://youtu.be/sw8S_Kv89IU " )
2019-12-29 20:33:51 -05:00
retcode , retmsg = pvc_cluster . initialize ( )
cleanup ( retcode , retmsg )
2018-06-05 01:39:59 -04:00
2018-06-06 01:07:59 -04:00
###############################################################################
# pvc
###############################################################################
2018-06-05 01:39:59 -04:00
@click.group ( context_settings = CONTEXT_SETTINGS )
2018-06-06 01:07:59 -04:00
@click.option (
2019-12-30 13:27:40 -05:00
' -c ' , ' --cluster ' , ' _cluster ' , envvar = ' PVC_CLUSTER ' , default = None ,
2018-06-06 01:07:59 -04:00
help = ' Zookeeper connection string. '
)
2019-12-26 11:20:57 -05:00
@click.option (
' -v ' , ' --debug ' , ' _debug ' , envvar = ' PVC_DEBUG ' , is_flag = True , default = False ,
help = ' Additional debug details. '
)
2019-12-30 13:27:40 -05:00
def cli ( _cluster , _debug ) :
2018-06-06 01:07:59 -04:00
"""
Parallel Virtual Cluster CLI management tool
2018-06-20 14:33:40 -04:00
2018-07-18 22:30:23 -04:00
Environment variables :
2019-12-30 13:27:40 -05:00
" PVC_CLUSTER " : Set the cluster to access instead of using - - cluster / - c
2018-10-25 11:54:05 -04:00
2019-12-30 13:27:40 -05:00
If no PVC_CLUSTER / - - cluster is specified , attempts first to load the " local " cluster , checking
2020-02-08 19:16:19 -05:00
for an API configuration in " /etc/pvc/pvcapid.yaml " . If this is also not found , abort .
2018-06-06 01:07:59 -04:00
"""
2019-12-26 11:20:57 -05:00
global config
2019-12-30 13:27:40 -05:00
store_data = get_store ( store_path )
config = get_config ( store_data , _cluster )
2020-01-02 11:19:11 -05:00
if not config . get ( ' badcfg ' , None ) :
config [ ' debug ' ] = _debug
2020-01-06 09:23:35 -05:00
click . echo (
' Using cluster " {} " - Host: " {} " Scheme: " {} " Prefix: " {} " ' . format (
config [ ' cluster ' ] ,
config [ ' api_host ' ] ,
config [ ' api_scheme ' ] ,
config [ ' api_prefix ' ]
) ,
err = True
)
click . echo ( ' ' , err = True )
2018-06-06 01:07:59 -04:00
2019-12-30 13:27:40 -05:00
config = dict ( )
2018-06-05 01:39:59 -04:00
#
# Click command tree
#
2020-01-02 11:18:46 -05:00
cli_cluster . add_command ( cluster_add )
cli_cluster . add_command ( cluster_remove )
cli_cluster . add_command ( cluster_list )
2018-10-14 02:01:35 -04:00
cli_node . add_command ( node_secondary )
cli_node . add_command ( node_primary )
2018-09-20 03:25:58 -04:00
cli_node . add_command ( node_flush )
cli_node . add_command ( node_ready )
cli_node . add_command ( node_unflush )
cli_node . add_command ( node_info )
cli_node . add_command ( node_list )
cli_vm . add_command ( vm_define )
2019-10-12 01:17:39 -04:00
cli_vm . add_command ( vm_meta )
2018-09-20 03:25:58 -04:00
cli_vm . add_command ( vm_modify )
cli_vm . add_command ( vm_undefine )
2019-06-27 11:19:48 -04:00
cli_vm . add_command ( vm_remove )
2019-03-12 21:09:54 -04:00
cli_vm . add_command ( vm_dump )
2018-09-20 03:25:58 -04:00
cli_vm . add_command ( vm_start )
cli_vm . add_command ( vm_restart )
cli_vm . add_command ( vm_shutdown )
cli_vm . add_command ( vm_stop )
2019-10-23 23:37:42 -04:00
cli_vm . add_command ( vm_disable )
2018-09-20 03:25:58 -04:00
cli_vm . add_command ( vm_move )
cli_vm . add_command ( vm_migrate )
cli_vm . add_command ( vm_unmigrate )
2019-08-07 13:42:01 -04:00
cli_vm . add_command ( vm_flush_locks )
2018-09-20 03:25:58 -04:00
cli_vm . add_command ( vm_info )
2019-04-11 19:06:06 -04:00
cli_vm . add_command ( vm_log )
2018-09-20 03:25:58 -04:00
cli_vm . add_command ( vm_list )
2018-09-21 23:43:30 -04:00
cli_network . add_command ( net_add )
2018-09-23 15:26:20 -04:00
cli_network . add_command ( net_modify )
2018-09-21 23:43:30 -04:00
cli_network . add_command ( net_remove )
cli_network . add_command ( net_info )
cli_network . add_command ( net_list )
2018-09-28 20:31:56 -04:00
cli_network . add_command ( net_dhcp )
cli_network . add_command ( net_acl )
net_dhcp . add_command ( net_dhcp_list )
2019-12-29 16:13:32 -05:00
net_dhcp . add_command ( net_dhcp_add )
net_dhcp . add_command ( net_dhcp_remove )
2018-09-21 23:43:30 -04:00
2018-10-17 00:23:27 -04:00
net_acl . add_command ( net_acl_add )
net_acl . add_command ( net_acl_remove )
net_acl . add_command ( net_acl_list )
2018-10-28 22:15:25 -04:00
ceph_osd . add_command ( ceph_osd_add )
2018-10-29 17:51:08 -04:00
ceph_osd . add_command ( ceph_osd_remove )
2018-11-01 22:00:59 -04:00
ceph_osd . add_command ( ceph_osd_in )
ceph_osd . add_command ( ceph_osd_out )
ceph_osd . add_command ( ceph_osd_set )
ceph_osd . add_command ( ceph_osd_unset )
2018-10-30 09:17:32 -04:00
ceph_osd . add_command ( ceph_osd_list )
2018-10-27 18:11:58 -04:00
2018-10-31 23:38:17 -04:00
ceph_pool . add_command ( ceph_pool_add )
ceph_pool . add_command ( ceph_pool_remove )
ceph_pool . add_command ( ceph_pool_list )
2018-10-27 18:11:58 -04:00
2019-06-19 00:12:44 -04:00
ceph_volume . add_command ( ceph_volume_add )
2020-02-09 20:42:56 -05:00
ceph_volume . add_command ( ceph_volume_upload )
2019-07-26 14:24:22 -04:00
ceph_volume . add_command ( ceph_volume_resize )
ceph_volume . add_command ( ceph_volume_rename )
2019-12-29 20:33:51 -05:00
ceph_volume . add_command ( ceph_volume_clone )
2019-06-19 00:12:44 -04:00
ceph_volume . add_command ( ceph_volume_remove )
ceph_volume . add_command ( ceph_volume_list )
ceph_volume . add_command ( ceph_volume_snapshot )
ceph_volume_snapshot . add_command ( ceph_volume_snapshot_add )
2019-07-28 23:00:35 -04:00
ceph_volume_snapshot . add_command ( ceph_volume_snapshot_rename )
2019-06-19 00:12:44 -04:00
ceph_volume_snapshot . add_command ( ceph_volume_snapshot_remove )
ceph_volume_snapshot . add_command ( ceph_volume_snapshot_list )
2020-01-11 16:02:53 -05:00
cli_storage . add_command ( ceph_status )
cli_storage . add_command ( ceph_util )
cli_storage . add_command ( ceph_osd )
cli_storage . add_command ( ceph_pool )
cli_storage . add_command ( ceph_volume )
2019-07-10 15:14:17 -04:00
2020-01-04 11:58:30 -05:00
provisioner_template_system . add_command ( provisioner_template_system_list )
provisioner_template_system . add_command ( provisioner_template_system_add )
2020-02-18 16:18:27 -05:00
provisioner_template_system . add_command ( provisioner_template_system_modify )
2020-01-04 11:58:30 -05:00
provisioner_template_system . add_command ( provisioner_template_system_remove )
provisioner_template_network . add_command ( provisioner_template_network_list )
provisioner_template_network . add_command ( provisioner_template_network_add )
provisioner_template_network . add_command ( provisioner_template_network_remove )
provisioner_template_network . add_command ( provisioner_template_network_vni )
provisioner_template_network_vni . add_command ( provisioner_template_network_vni_add )
provisioner_template_network_vni . add_command ( provisioner_template_network_vni_remove )
provisioner_template_storage . add_command ( provisioner_template_storage_list )
provisioner_template_storage . add_command ( provisioner_template_storage_add )
provisioner_template_storage . add_command ( provisioner_template_storage_remove )
provisioner_template_storage . add_command ( provisioner_template_storage_disk )
provisioner_template_storage_disk . add_command ( provisioner_template_storage_disk_add )
provisioner_template_storage_disk . add_command ( provisioner_template_storage_disk_remove )
provisioner_template . add_command ( provisioner_template_system )
provisioner_template . add_command ( provisioner_template_network )
provisioner_template . add_command ( provisioner_template_storage )
provisioner_template . add_command ( provisioner_template_list )
2019-12-30 13:27:40 -05:00
2020-01-04 13:04:01 -05:00
provisioner_userdata . add_command ( provisioner_userdata_list )
provisioner_userdata . add_command ( provisioner_userdata_add )
provisioner_userdata . add_command ( provisioner_userdata_modify )
provisioner_userdata . add_command ( provisioner_userdata_remove )
provisioner_script . add_command ( provisioner_script_list )
provisioner_script . add_command ( provisioner_script_add )
provisioner_script . add_command ( provisioner_script_modify )
provisioner_script . add_command ( provisioner_script_remove )
2020-02-17 22:52:49 -05:00
provisioner_ova . add_command ( provisioner_ova_list )
provisioner_ova . add_command ( provisioner_ova_upload )
provisioner_ova . add_command ( provisioner_ova_remove )
2020-01-04 14:06:36 -05:00
provisioner_profile . add_command ( provisioner_profile_list )
provisioner_profile . add_command ( provisioner_profile_add )
provisioner_profile . add_command ( provisioner_profile_modify )
provisioner_profile . add_command ( provisioner_profile_remove )
2020-01-02 11:18:46 -05:00
cli_provisioner . add_command ( provisioner_template )
2020-01-04 13:04:01 -05:00
cli_provisioner . add_command ( provisioner_userdata )
cli_provisioner . add_command ( provisioner_script )
2020-02-17 22:52:49 -05:00
cli_provisioner . add_command ( provisioner_ova )
2020-01-04 14:06:36 -05:00
cli_provisioner . add_command ( provisioner_profile )
2020-01-04 14:31:22 -05:00
cli_provisioner . add_command ( provisioner_create )
cli_provisioner . add_command ( provisioner_status )
2020-01-02 11:18:46 -05:00
2020-01-09 10:53:27 -05:00
cli_maintenance . add_command ( maintenance_on )
cli_maintenance . add_command ( maintenance_off )
2020-01-02 11:18:46 -05:00
cli . add_command ( cli_cluster )
2018-09-20 03:25:58 -04:00
cli . add_command ( cli_node )
cli . add_command ( cli_vm )
2018-09-21 23:43:30 -04:00
cli . add_command ( cli_network )
2019-07-10 15:14:17 -04:00
cli . add_command ( cli_storage )
2020-01-02 11:18:46 -05:00
cli . add_command ( cli_provisioner )
2020-01-09 10:53:27 -05:00
cli . add_command ( cli_maintenance )
2019-10-22 11:23:12 -04:00
cli . add_command ( status_cluster )
2018-06-06 01:20:09 -04:00
cli . add_command ( init_cluster )
2018-06-05 01:39:59 -04:00
2019-12-30 13:27:40 -05:00
2018-06-05 01:39:59 -04:00
#
# Main entry point
#
def main ( ) :
return cli ( obj = { } )
if __name__ == ' __main__ ' :
main ( )
2018-06-14 11:57:36 -04:00