Add OVA script support

1. Ensure that system_template and script are not nullable in the DB.
2. Ensure that the CLI and API enforce the above and clean up CLI
arguments for profile add.
3. Ensure that, before uploading OVAs, a 'default_ova' provisioning
script is present.
4. Use the 'default_ova' script for new OVA uploads.
5. Ensure that OVA details are properly added to the vm_data dict in the
provisioner vmbuilder.
This commit is contained in:
Joshua Boniface 2022-10-06 10:27:08 -04:00
parent bffab7a5a1
commit 7a3870fc44
7 changed files with 104 additions and 21 deletions

View File

@ -0,0 +1,38 @@
"""PVC version 0.9.55
Revision ID: 88fa0d88a9f8
Revises: 5c2109dbbeae
Create Date: 2022-10-06 10:33:38.784497
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '88fa0d88a9f8'
down_revision = '5c2109dbbeae'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('profile', 'script',
existing_type=sa.INTEGER(),
nullable=False)
op.alter_column('profile', 'system_template',
existing_type=sa.INTEGER(),
nullable=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('profile', 'system_template',
existing_type=sa.INTEGER(),
nullable=True)
op.alter_column('profile', 'script',
existing_type=sa.INTEGER(),
nullable=True)
# ### end Alembic commands ###

View File

@ -7352,11 +7352,19 @@ class API_Provisioner_Profile_Root(Resource):
"required": True, "required": True,
"helptext": "A profile type must be specified.", "helptext": "A profile type must be specified.",
}, },
{"name": "system_template"}, {
"name": "system_template",
"required": True,
"helptext": "A system_template must be specified.",
},
{"name": "network_template"}, {"name": "network_template"},
{"name": "storage_template"}, {"name": "storage_template"},
{"name": "userdata"}, {"name": "userdata"},
{"name": "script"}, {
"name": "script",
"required": True,
"helptext": "A script must be specified.",
},
{"name": "ova"}, {"name": "ova"},
{"name": "arg", "action": "append"}, {"name": "arg", "action": "append"},
] ]
@ -7385,12 +7393,12 @@ class API_Provisioner_Profile_Root(Resource):
- in: query - in: query
name: script name: script
type: string type: string
required: false required: true
description: Script name description: Script name
- in: query - in: query
name: system_template name: system_template
type: string type: string
required: false required: true
description: System template name description: System template name
- in: query - in: query
name: network_template name: network_template
@ -7473,11 +7481,19 @@ class API_Provisioner_Profile_Element(Resource):
"required": True, "required": True,
"helptext": "A profile type must be specified.", "helptext": "A profile type must be specified.",
}, },
{"name": "system_template"}, {
"name": "system_template",
"required": True,
"helptext": "A system_template must be specified.",
},
{"name": "network_template"}, {"name": "network_template"},
{"name": "storage_template"}, {"name": "storage_template"},
{"name": "userdata"}, {"name": "userdata"},
{"name": "script"}, {
"name": "script",
"required": True,
"helptext": "A script must be specified.",
},
{"name": "ova"}, {"name": "ova"},
{"name": "arg", "action": "append"}, {"name": "arg", "action": "append"},
] ]
@ -7511,17 +7527,17 @@ class API_Provisioner_Profile_Element(Resource):
- in: query - in: query
name: network_template name: network_template
type: string type: string
required: true required: false
description: Network template name description: Network template name
- in: query - in: query
name: storage_template name: storage_template
type: string type: string
required: true required: false
description: Storage template name description: Storage template name
- in: query - in: query
name: userdata name: userdata
type: string type: string
required: true required: false
description: Userdata template name description: Userdata template name
- in: query - in: query
name: ova name: ova

View File

@ -230,11 +230,13 @@ class DBProfile(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False, unique=True) name = db.Column(db.Text, nullable=False, unique=True)
profile_type = db.Column(db.Text, nullable=False) profile_type = db.Column(db.Text, nullable=False)
system_template = db.Column(db.Integer, db.ForeignKey("system_template.id")) system_template = db.Column(
db.Integer, db.ForeignKey("system_template.id"), nullable=False
)
network_template = db.Column(db.Integer, db.ForeignKey("network_template.id")) network_template = db.Column(db.Integer, db.ForeignKey("network_template.id"))
storage_template = db.Column(db.Integer, db.ForeignKey("storage_template.id")) storage_template = db.Column(db.Integer, db.ForeignKey("storage_template.id"))
userdata = db.Column(db.Integer, db.ForeignKey("userdata.id")) userdata = db.Column(db.Integer, db.ForeignKey("userdata.id"))
script = db.Column(db.Integer, db.ForeignKey("script.id")) script = db.Column(db.Integer, db.ForeignKey("script.id"), nullable=False)
ova = db.Column(db.Integer, db.ForeignKey("ova.id")) ova = db.Column(db.Integer, db.ForeignKey("ova.id"))
arguments = db.Column(db.Text) arguments = db.Column(db.Text)

View File

@ -168,6 +168,15 @@ def delete_ova(zkhandler, name):
@ZKConnection(config) @ZKConnection(config)
def upload_ova(zkhandler, pool, name, ova_size): def upload_ova(zkhandler, pool, name, ova_size):
# Check that we have a default_ova provisioning script
_, retcode = provisioner.list_script("default_ova", is_fuzzy=False)
if retcode != "200":
output = {
"message": "Did not find a 'default_ova' provisioning script. Please add one with that name, either the example from '/usr/share/pvc/provisioner/examples/script/2-ova.py' or a custom one, before uploading OVAs."
}
retcode = 400
return output, retcode
ova_archive = None ova_archive = None
# Cleanup function # Cleanup function
@ -402,7 +411,7 @@ def upload_ova(zkhandler, pool, name, ova_size):
None, None,
None, None,
userdata=None, userdata=None,
script=None, script="default_ova",
ova=name, ova=name,
arguments=None, arguments=None,
) )

View File

@ -277,6 +277,8 @@ def create_vm(
vm_data["script"] = db_row.get("script") vm_data["script"] = db_row.get("script")
else: else:
vm_data["script"] = None vm_data["script"] = None
if profile_data.get("profile_type") == "ova":
query = "SELECT * FROM ova WHERE id = %s" query = "SELECT * FROM ova WHERE id = %s"
args = (profile_data["ova"],) args = (profile_data["ova"],)
db_cur.execute(query, args) db_cur.execute(query, args)
@ -285,6 +287,7 @@ def create_vm(
query = "SELECT * FROM ova_volume WHERE ova = %s" query = "SELECT * FROM ova_volume WHERE ova = %s"
args = (profile_data["ova"],) args = (profile_data["ova"],)
db_cur.execute(query, args) db_cur.execute(query, args)
# Replace the existing volumes list with our OVA volume list
vm_data["volumes"] = db_cur.fetchall() vm_data["volumes"] = db_cur.fetchall()
retcode, stdout, stderr = pvc_common.run_os_command("uname -m") retcode, stdout, stderr = pvc_common.run_os_command("uname -m")

View File

@ -5265,7 +5265,8 @@ def provisioner_profile_list(limit):
"-s", "-s",
"--system-template", "--system-template",
"system_template", "system_template",
help="The system template for the profile.", required=True,
help="The system template for the profile (required).",
) )
@click.option( @click.option(
"-n", "-n",
@ -5280,10 +5281,24 @@ def provisioner_profile_list(limit):
help="The storage template for the profile.", help="The storage template for the profile.",
) )
@click.option( @click.option(
"-u", "--userdata", "userdata", help="The userdata document for the profile." "-u",
"--userdata",
"userdata",
help="The userdata document for the profile.",
)
@click.option(
"-x",
"--script",
"script",
required=True,
help="The script for the profile (required).",
)
@click.option(
"-o",
"--ova",
"ova",
help="The OVA image for the profile; set automatically with 'provisioner ova upload'.",
) )
@click.option("-x", "--script", "script", help="The script for the profile.")
@click.option("-o", "--ova", "ova", help="The OVA image for the profile.")
@click.option( @click.option(
"-a", "-a",
"--script-arg", "--script-arg",

View File

@ -3025,14 +3025,14 @@
"description": "Script name", "description": "Script name",
"in": "query", "in": "query",
"name": "script", "name": "script",
"required": false, "required": true,
"type": "string" "type": "string"
}, },
{ {
"description": "System template name", "description": "System template name",
"in": "query", "in": "query",
"name": "system_template", "name": "system_template",
"required": false, "required": true,
"type": "string" "type": "string"
}, },
{ {
@ -3165,21 +3165,21 @@
"description": "Network template name", "description": "Network template name",
"in": "query", "in": "query",
"name": "network_template", "name": "network_template",
"required": true, "required": false,
"type": "string" "type": "string"
}, },
{ {
"description": "Storage template name", "description": "Storage template name",
"in": "query", "in": "query",
"name": "storage_template", "name": "storage_template",
"required": true, "required": false,
"type": "string" "type": "string"
}, },
{ {
"description": "Userdata template name", "description": "Userdata template name",
"in": "query", "in": "query",
"name": "userdata", "name": "userdata",
"required": true, "required": false,
"type": "string" "type": "string"
}, },
{ {