update: updated tests to have method calls for particular cases. Added more tests.

This commit is contained in:
Andrey Melnikov
2020-07-01 16:07:20 -07:00
parent c5d1ec9b1f
commit ed621633df
11 changed files with 491 additions and 84 deletions

View File

@@ -44,6 +44,7 @@ s3:
}, },
Data: map[string]string{ Data: map[string]string{
"ONEPANEL_HOST": "demo.onepanel.site", "ONEPANEL_HOST": "demo.onepanel.site",
"ONEPANEL_DOMAIN": "demo.onepanel.site",
"artifactRepository": configArtifactRepository, "artifactRepository": configArtifactRepository,
"applicationNodePoolLabel": "beta.kubernetes.io/instance-type", "applicationNodePoolLabel": "beta.kubernetes.io/instance-type",
"applicationNodePoolOptions": ` "applicationNodePoolOptions": `

View File

@@ -340,6 +340,7 @@ func (c *Client) ValidateWorkflowExecution(namespace string, manifest []byte) (e
// CreateWorkflowExecution creates an argo workflow execution and related resources. // CreateWorkflowExecution creates an argo workflow execution and related resources.
// If workflow.Name is set, it is used instead of a generated name. // If workflow.Name is set, it is used instead of a generated name.
// If there is a parameter named "workflow-execution-name" in workflow.Parameters, it is set as the name.
func (c *Client) CreateWorkflowExecution(namespace string, workflow *WorkflowExecution, workflowTemplate *WorkflowTemplate) (*WorkflowExecution, error) { func (c *Client) CreateWorkflowExecution(namespace string, workflow *WorkflowExecution, workflowTemplate *WorkflowTemplate) (*WorkflowExecution, error) {
opts := &WorkflowExecutionOptions{ opts := &WorkflowExecutionOptions{
Labels: make(map[string]string), Labels: make(map[string]string),
@@ -350,6 +351,10 @@ func (c *Client) CreateWorkflowExecution(namespace string, workflow *WorkflowExe
opts.Name = workflow.Name opts.Name = workflow.Name
} }
if workflowExecutionName := workflow.GetParameterValue("workflow-execution-name"); workflowExecutionName != nil {
opts.Name = *workflowExecutionName
}
nameUID, err := uid2.GenerateUID(workflowTemplate.Name, 63) nameUID, err := uid2.GenerateUID(workflowTemplate.Name, 63)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -75,6 +75,17 @@ func (we *WorkflowExecution) LoadParametersFromBytes() ([]Parameter, error) {
return we.Parameters, err return we.Parameters, err
} }
// GetParameterValue returns the value of the parameter with the given name, or nil if there is no such parameter
func (we *WorkflowExecution) GetParameterValue(name string) *string {
for _, p := range we.Parameters {
if p.Name == name {
return p.Value
}
}
return nil
}
// getWorkflowExecutionColumns returns all of the columns for workflowExecution modified by alias, destination. // getWorkflowExecutionColumns returns all of the columns for workflowExecution modified by alias, destination.
// see formatColumnSelect // see formatColumnSelect
func getWorkflowExecutionColumns(aliasAndDestination ...string) []string { func getWorkflowExecutionColumns(aliasAndDestination ...string) []string {

View File

@@ -164,6 +164,8 @@ func (c *Client) countWorkflowTemplateSelectBuilder(namespace string) sq.SelectB
return sb return sb
} }
// workflowTemplatesVersionSelectBuilder selects data from workflow template versions joined to a workflow template
// the versions/template are filtered by the workflow template's namespace.
func (c *Client) workflowTemplatesVersionSelectBuilder(namespace string) sq.SelectBuilder { func (c *Client) workflowTemplatesVersionSelectBuilder(namespace string) sq.SelectBuilder {
sb := sb.Select(getWorkflowTemplateVersionColumns("wtv")...). sb := sb.Select(getWorkflowTemplateVersionColumns("wtv")...).
From("workflow_template_versions wtv"). From("workflow_template_versions wtv").
@@ -176,7 +178,7 @@ func (c *Client) workflowTemplatesVersionSelectBuilder(namespace string) sq.Sele
} }
// GetWorkflowTemplateDB returns a WorkflowTemplate from the database that is not archived, should one exist. // GetWorkflowTemplateDB returns a WorkflowTemplate from the database that is not archived, should one exist.
func (c *Client) GetWorkflowTemplateDB(namespace, name string) (workflowTemplate *WorkflowTemplate, err error) { func (c *Client) getWorkflowTemplateDB(namespace, name string) (workflowTemplate *WorkflowTemplate, err error) {
workflowTemplate = &WorkflowTemplate{} workflowTemplate = &WorkflowTemplate{}
sb := c.workflowTemplatesSelectBuilder(namespace). sb := c.workflowTemplatesSelectBuilder(namespace).
@@ -190,9 +192,11 @@ func (c *Client) GetWorkflowTemplateDB(namespace, name string) (workflowTemplate
return return
} }
// GetWorkflowTemplateVersionDB will return a WorkflowTemplateVersion given the arguments. // getWorkflowTemplateVersionDB will return a WorkflowTemplateVersion given the arguments.
// version can be a number as a string, or the string "latest" to get the latest. // version can be a number as a string, or the string "latest" to get the latest.
func (c *Client) GetWorkflowTemplateVersionDB(namespace, name, version string) (workflowTemplateVersion *WorkflowTemplateVersion, err error) { func (c *Client) getWorkflowTemplateVersionDB(namespace, name, version string) (workflowTemplateVersion *WorkflowTemplateVersion, err error) {
workflowTemplateVersion = &WorkflowTemplateVersion{}
whereMap := sq.Eq{ whereMap := sq.Eq{
"wt.name": name, "wt.name": name,
} }
@@ -212,8 +216,8 @@ func (c *Client) GetWorkflowTemplateVersionDB(namespace, name, version string) (
} }
// GetLatestWorkflowTemplateVersionDB returns the latest WorkflowTemplateVersion // GetLatestWorkflowTemplateVersionDB returns the latest WorkflowTemplateVersion
func (c *Client) GetLatestWorkflowTemplateVersionDB(namespace, name string) (workflowTemplateVersion *WorkflowTemplateVersion, err error) { func (c *Client) getLatestWorkflowTemplateVersionDB(namespace, name string) (workflowTemplateVersion *WorkflowTemplateVersion, err error) {
return c.GetWorkflowTemplateVersionDB(namespace, name, "latest") return c.getWorkflowTemplateVersionDB(namespace, name, "latest")
} }
func (c *Client) getWorkflowTemplateById(id uint64) (workflowTemplate *WorkflowTemplate, err error) { func (c *Client) getWorkflowTemplateById(id uint64) (workflowTemplate *WorkflowTemplate, err error) {
@@ -233,13 +237,16 @@ func (c *Client) getWorkflowTemplateById(id uint64) (workflowTemplate *WorkflowT
return return
} }
// If version is 0, the latest workflow template is fetched. // getWorkflowTemplate gets the workflowtemplate given the input data.
// it also loads the argo workflow and labels data.
// If version is <= 0, the latest workflow template is fetched.
// If not found, (nil, nil) is returned
func (c *Client) getWorkflowTemplate(namespace, uid string, version int64) (workflowTemplate *WorkflowTemplate, err error) { func (c *Client) getWorkflowTemplate(namespace, uid string, version int64) (workflowTemplate *WorkflowTemplate, err error) {
workflowTemplate = &WorkflowTemplate{ workflowTemplate = &WorkflowTemplate{
WorkflowExecutionStatisticReport: &WorkflowExecutionStatisticReport{}, WorkflowExecutionStatisticReport: &WorkflowExecutionStatisticReport{},
} }
// A new workflow template version is created upon a change, so we use it's createdAt // A new workflow template version is created upon a change, so we use it's created_at
// as a modified_at for the workflow template. // as a modified_at for the workflow template.
sb := c.workflowTemplatesSelectBuilder(namespace). sb := c.workflowTemplatesSelectBuilder(namespace).
Columns("wtv.manifest", "wtv.version", "wtv.id workflow_template_version_id", "wtv.created_at modified_at"). Columns("wtv.manifest", "wtv.version", "wtv.id workflow_template_version_id", "wtv.created_at modified_at").
@@ -249,7 +256,7 @@ func (c *Client) getWorkflowTemplate(namespace, uid string, version int64) (work
"wt.is_archived": false, "wt.is_archived": false,
}) })
if version == 0 { if version <= 0 {
sb = sb.Where(sq.Eq{"wtv.is_latest": true}) sb = sb.Where(sq.Eq{"wtv.is_latest": true})
} else { } else {
sb = sb.Where(sq.Eq{"wtv.version": version}) sb = sb.Where(sq.Eq{"wtv.version": version})
@@ -292,7 +299,7 @@ func (c *Client) getWorkflowTemplate(namespace, uid string, version int64) (work
} }
func (c *Client) listWorkflowTemplateVersions(namespace, uid string) (workflowTemplateVersions []*WorkflowTemplate, err error) { func (c *Client) listWorkflowTemplateVersions(namespace, uid string) (workflowTemplateVersions []*WorkflowTemplate, err error) {
dbVersions, err := c.listDBWorkflowTemplateVersions(namespace, uid) dbVersions, err := c.selectWorkflowTemplateVersionsDB(namespace, uid)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -327,8 +334,10 @@ func (c *Client) listWorkflowTemplateVersions(namespace, uid string) (workflowTe
return return
} }
func (c *Client) listWorkflowTemplates(namespace string, paginator *pagination.PaginationRequest) (workflowTemplateVersions []*WorkflowTemplate, err error) { // selectWorkflowTemplatesDB loads workflow templates from the database for the input namespace
workflowTemplateVersions = []*WorkflowTemplate{} // it also selects the total number of versions and latest version id
func (c *Client) selectWorkflowTemplatesDB(namespace string, paginator *pagination.PaginationRequest) (workflowTemplates []*WorkflowTemplate, err error) {
workflowTemplates = make([]*WorkflowTemplate, 0)
sb := c.workflowTemplatesSelectBuilder(namespace). sb := c.workflowTemplatesSelectBuilder(namespace).
Column("COUNT(wtv.*) versions, MAX(wtv.id) workflow_template_version_id"). Column("COUNT(wtv.*) versions, MAX(wtv.id) workflow_template_version_id").
@@ -339,28 +348,24 @@ func (c *Client) listWorkflowTemplates(namespace string, paginator *pagination.P
"wt.is_system": false, "wt.is_system": false,
}). }).
OrderBy("wt.created_at DESC") OrderBy("wt.created_at DESC")
sb = *paginator.ApplyToSelect(&sb) sb = *paginator.ApplyToSelect(&sb)
query, args, err := sb.ToSql()
if err != nil {
return
}
err = c.DB.Select(&workflowTemplateVersions, query, args...) err = c.DB.Selectx(&workflowTemplates, sb)
return return
} }
// CountWorkflowTemplates counts the total number of workflow templates for the given namespace
// archived, and system templates are ignored.
func (c *Client) CountWorkflowTemplates(namespace string) (count int, err error) { func (c *Client) CountWorkflowTemplates(namespace string) (count int, err error) {
err = sb.Select("COUNT( DISTINCT( wt.id ))"). err = sb.Select("COUNT(*)").
From("workflow_templates wt"). From("workflow_templates wt").
Join("workflow_template_versions wtv ON wtv.workflow_template_id = wt.id").
Where(sq.Eq{ Where(sq.Eq{
"wt.namespace": namespace, "wt.namespace": namespace,
"wt.is_archived": false, "wt.is_archived": false,
"wt.is_system": false, "wt.is_system": false,
}). }).
RunWith(c.DB.DB). RunWith(c.DB).
QueryRow(). QueryRow().
Scan(&count) Scan(&count)
@@ -406,6 +411,9 @@ func (c *Client) CreateWorkflowTemplate(namespace string, workflowTemplate *Work
// CreateWorkflowTemplateVersion creates a new workflow template version including argo resources. // CreateWorkflowTemplateVersion creates a new workflow template version including argo resources.
// It marks any older workflow template versions as not latest // It marks any older workflow template versions as not latest
//
// Pre-condition: a Workflow Template version already exists
// Post-condition: the input workflow template will have it's fields updated so it matches the new version data.
func (c *Client) CreateWorkflowTemplateVersion(namespace string, workflowTemplate *WorkflowTemplate) (*WorkflowTemplate, error) { func (c *Client) CreateWorkflowTemplateVersion(namespace string, workflowTemplate *WorkflowTemplate) (*WorkflowTemplate, error) {
if workflowTemplate.UID == "" { if workflowTemplate.UID == "" {
return nil, fmt.Errorf("uid required for CreateWorkflowTemplateVersion") return nil, fmt.Errorf("uid required for CreateWorkflowTemplateVersion")
@@ -568,7 +576,7 @@ func (c *Client) ListWorkflowTemplateVersions(namespace, uid string) (workflowTe
} }
func (c *Client) ListWorkflowTemplates(namespace string, paginator *pagination.PaginationRequest) (workflowTemplateVersions []*WorkflowTemplate, err error) { func (c *Client) ListWorkflowTemplates(namespace string, paginator *pagination.PaginationRequest) (workflowTemplateVersions []*WorkflowTemplate, err error) {
workflowTemplateVersions, err = c.listWorkflowTemplates(namespace, paginator) workflowTemplateVersions, err = c.selectWorkflowTemplatesDB(namespace, paginator)
if err != nil { if err != nil {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"Namespace": namespace, "Namespace": namespace,
@@ -805,28 +813,22 @@ func (c *Client) listArgoWorkflowTemplates(namespace, workflowTemplateUid string
return &templates, nil return &templates, nil
} }
func (c *Client) listDBWorkflowTemplateVersions(namespace, workflowTemplateUID string) ([]*WorkflowTemplateVersion, error) { // listDBWorkflowTemplateVersions gets all of the workflow template versions for a specified workflow template uid
versions := make([]*WorkflowTemplateVersion, 0) // archived ones are ignored. Returned in created_at desc order.
func (c *Client) selectWorkflowTemplateVersionsDB(namespace, workflowTemplateUID string) (versions []*WorkflowTemplateVersion, err error) {
versions = make([]*WorkflowTemplateVersion, 0)
sb := c.workflowTemplatesVersionSelectBuilder(namespace). sb := c.workflowTemplatesVersionSelectBuilder(namespace).
Columns(`wt.id "workflow_template.id"`, `wt.created_at "workflow_template.created_at"`). Columns(getWorkflowTemplateColumns("wt", "workflow_template")...).
Columns(`wt.name "workflow_template.name"`, `wt.is_archived "workflow_template.is_archived"`).
Where(sq.Eq{ Where(sq.Eq{
"wt.uid": workflowTemplateUID, "wt.uid": workflowTemplateUID,
"wt.is_archived": false, "wt.is_archived": false,
}). }).
OrderBy("wtv.created_at DESC") OrderBy("wtv.created_at DESC")
query, args, err := sb.ToSql() err = c.DB.Selectx(&versions, sb)
if err != nil {
return versions, err
}
if err := c.DB.Select(&versions, query, args...); err != nil { return
return versions, err
}
return versions, nil
} }
// prefix is the label prefix. // prefix is the label prefix.

View File

@@ -2,6 +2,7 @@ package v1
import ( import (
"database/sql" "database/sql"
"fmt"
"github.com/onepanelio/core/pkg/util" "github.com/onepanelio/core/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
@@ -85,19 +86,19 @@ templates:
optional: true optional: true
` `
// Test_GetWorkflowTemplateDB_Empty attempts to get a WorkflowTemplate when there isn't one. // testClientGetWorkflowTemplateDBEmpty attempts to get a WorkflowTemplate when there isn't one.
// this should fail. // this should fail.
func Test_GetWorkflowTemplateDB_Empty(t *testing.T) { func testClientGetWorkflowTemplateDBEmpty(t *testing.T) {
c := DefaultTestClient() c := DefaultTestClient()
clearDatabase(t) clearDatabase(t)
_, err := c.GetWorkflowTemplateDB("test", "test") _, err := c.getWorkflowTemplateDB("test", "test")
assert.Equal(t, sql.ErrNoRows, err) assert.Equal(t, sql.ErrNoRows, err)
} }
// Test_GetWorkflowTemplateDB_Exists gets a WorkflowTemplate when there is one // testClientGetWorkflowTemplateDBExists gets a WorkflowTemplate when there is one
// this should succeed // this should succeed
func Test_GetWorkflowTemplateDB_Exists(t *testing.T) { func testClientGetWorkflowTemplateDBExists(t *testing.T) {
c := DefaultTestClient() c := DefaultTestClient()
clearDatabase(t) clearDatabase(t)
@@ -108,34 +109,18 @@ func Test_GetWorkflowTemplateDB_Exists(t *testing.T) {
_, err := c.CreateWorkflowTemplate("onepanel", workflowTemplate) _, err := c.CreateWorkflowTemplate("onepanel", workflowTemplate)
assert.Nil(t, err) assert.Nil(t, err)
_, err = c.GetWorkflowTemplateDB("onepanel", "test") _, err = c.getWorkflowTemplateDB("onepanel", "test")
assert.Nil(t, err) assert.Nil(t, err)
} }
// Test_GetWorkflowTemplateDB_InsertSameName attempts to insert a WorkflowTemplate with the same name // TestClient_getWorkflowTemplateDB tests getting a workflow template from the database
// this should fail func TestClient_getWorkflowTemplateDB(t *testing.T) {
func Test_GetWorkflowTemplateDB_InsertSameName(t *testing.T) { testClientGetWorkflowTemplateDBEmpty(t)
c := DefaultTestClient() testClientGetWorkflowTemplateDBExists(t)
clearDatabase(t)
workflowTemplate := &WorkflowTemplate{
Name: "test",
Manifest: defaultWorkflowTemplate,
}
_, err := c.CreateWorkflowTemplate("onepanel", workflowTemplate)
assert.Nil(t, err)
_, err = c.CreateWorkflowTemplate("onepanel", workflowTemplate)
assert.NotNil(t, err)
assert.IsType(t, &util.UserError{}, err)
userErr := err.(*util.UserError)
assert.Equal(t, userErr.Code, codes.AlreadyExists)
} }
// TestClient_CreateWorkflowTemplate_Success makes sure a correct workflow template is created correctly // testClientCreateWorkflowTemplateSuccess makes sure a correct workflow template is created correctly
func TestClient_CreateWorkflowTemplate_Success(t *testing.T) { func testClientCreateWorkflowTemplateSuccess(t *testing.T) {
c := DefaultTestClient() c := DefaultTestClient()
clearDatabase(t) clearDatabase(t)
@@ -149,11 +134,11 @@ func TestClient_CreateWorkflowTemplate_Success(t *testing.T) {
assert.NotNil(t, wft.ArgoWorkflowTemplate) assert.NotNil(t, wft.ArgoWorkflowTemplate)
} }
// TestClient_CreateWorkflowTemplate_Timestamp makes sure we can create mulitple // testClientCreateWorkflowTemplateTimestamp makes sure we can create mulitple
// workflow templtate versions one after another with practically no time delay. // workflow templtate versions one after another with practically no time delay.
// This handles an edge case where versions were set using second time precision and could fail in migrations // This handles an edge case where versions were set using second time precision and could fail in migrations
// as they were created one after another. // as they were created one after another.
func TestClient_CreateWorkflowTemplate_Timestamp(t *testing.T) { func testClientCreateWorkflowTemplateTimestamp(t *testing.T) {
c := DefaultTestClient() c := DefaultTestClient()
clearDatabase(t) clearDatabase(t)
@@ -174,3 +159,187 @@ func TestClient_CreateWorkflowTemplate_Timestamp(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, wft.ArgoWorkflowTemplate) assert.NotNil(t, wft.ArgoWorkflowTemplate)
} }
// testClientCreateWorkflowTemplateInsertSameName attempts to insert a WorkflowTemplate with the same name
// this should fail
func testClientCreateWorkflowTemplateInsertSameName(t *testing.T) {
c := DefaultTestClient()
clearDatabase(t)
workflowTemplate := &WorkflowTemplate{
Name: "test",
Manifest: defaultWorkflowTemplate,
}
_, err := c.CreateWorkflowTemplate("onepanel", workflowTemplate)
assert.Nil(t, err)
_, err = c.CreateWorkflowTemplate("onepanel", workflowTemplate)
assert.NotNil(t, err)
assert.IsType(t, &util.UserError{}, err)
userErr := err.(*util.UserError)
assert.Equal(t, userErr.Code, codes.AlreadyExists)
}
// TestClient_CreateWorkflowTemplate tests creating a workflow template
func TestClient_CreateWorkflowTemplate(t *testing.T) {
testClientCreateWorkflowTemplateInsertSameName(t)
testClientCreateWorkflowTemplateSuccess(t)
testClientCreateWorkflowTemplateTimestamp(t)
}
// testClientPrivateGetWorkflowTemplateSuccess gets a workflow template with no error conditions encountered
func testClientPrivateGetWorkflowTemplateSuccess(t *testing.T) {
c := DefaultTestClient()
clearDatabase(t)
namespace := "onepanel"
workflowTemplate := &WorkflowTemplate{
Name: "test",
Manifest: defaultWorkflowTemplate,
}
created, _ := c.CreateWorkflowTemplate(namespace, workflowTemplate)
wt, err := c.getWorkflowTemplate(namespace, created.UID, 0)
assert.NotNil(t, wt)
assert.Nil(t, err)
}
// testClientGetWorkflowTemplateSuccessVersion gets a workflow template for a specific version with no error conditions encountered
func testClientPrivateGetWorkflowTemplateSuccessVersion(t *testing.T) {
c := DefaultTestClient()
clearDatabase(t)
namespace := "onepanel"
workflowTemplate := &WorkflowTemplate{
Name: "test",
Manifest: defaultWorkflowTemplate,
}
created, _ := c.CreateWorkflowTemplate(namespace, workflowTemplate)
c.CreateWorkflowTemplateVersion(namespace, workflowTemplate)
wt, err := c.getWorkflowTemplate(namespace, created.UID, created.Version)
assert.NotNil(t, wt)
assert.Nil(t, err)
assert.Equal(t, created.Version, wt.Version)
assert.Equal(t, created.Manifest, wt.Manifest)
}
// testClientGetWorkflowTemplateNotFound attempts to get a not-found workflow template
func testClientPrivateGetWorkflowTemplateNotFound(t *testing.T) {
c := DefaultTestClient()
clearDatabase(t)
wt, err := c.getWorkflowTemplate("onepanel", "uid-not-found", 0)
assert.Nil(t, wt)
assert.Nil(t, err)
}
// Test_getWorkflowTemplate tests getting a workflow template
func Test_getWorkflowTemplate(t *testing.T) {
testClientPrivateGetWorkflowTemplateSuccess(t)
testClientPrivateGetWorkflowTemplateNotFound(t)
testClientPrivateGetWorkflowTemplateSuccessVersion(t)
}
func TestClient_getWorkflowTemplateVersionDB(t *testing.T) {
c := DefaultTestClient()
clearDatabase(t)
namespace := "onepanel"
name := "test"
workflowTemplate := &WorkflowTemplate{
Name: name,
Manifest: defaultWorkflowTemplate,
}
original, _ := c.CreateWorkflowTemplate(namespace, workflowTemplate)
versionAsString := fmt.Sprintf("%v", original.Version)
originalRes, err := c.getWorkflowTemplateVersionDB(namespace, name, versionAsString)
assert.Nil(t, err)
assert.Equal(t, original.Version, originalRes.Version)
}
// testClientCreateWorkflowTemplateVersionNew makes sure you can successfully create a new workflow template version
func testClientCreateWorkflowTemplateVersionNew(t *testing.T) {
c := DefaultTestClient()
clearDatabase(t)
namespace := "onepanel"
workflowTemplate := &WorkflowTemplate{
Name: "test",
Manifest: defaultWorkflowTemplate,
}
c.CreateWorkflowTemplate(namespace, workflowTemplate)
_, err := c.CreateWorkflowTemplateVersion(namespace, workflowTemplate)
assert.Nil(t, err)
}
// testClientCreateWorkflowTemplateVersionMarkOldNotLatest makes sure older versions are no longer marked as latest
func testClientCreateWorkflowTemplateVersionMarkOldNotLatest(t *testing.T) {
c := DefaultTestClient()
clearDatabase(t)
namespace := "onepanel"
name := "test"
workflowTemplate := &WorkflowTemplate{
Name: name,
Manifest: defaultWorkflowTemplate,
}
original, _ := c.CreateWorkflowTemplate(namespace, workflowTemplate)
originalVersionAsString := fmt.Sprintf("%v", original.Version)
c.CreateWorkflowTemplateVersion(namespace, workflowTemplate)
updated, _ := c.getWorkflowTemplateVersionDB(namespace, name, originalVersionAsString)
assert.False(t, updated.IsLatest)
}
// Test_getWorkflowTemplate_SuccessVersion tests cases for creating a workflow template version
func TestClient_CreateWorkflowTemplateVersion(t *testing.T) {
testClientCreateWorkflowTemplateVersionNew(t)
testClientCreateWorkflowTemplateVersionMarkOldNotLatest(t)
}
// testGetWorkflowTemplateSuccess gets a workflow template with no error conditions encountered
func testClientGetWorkflowTemplateSuccess(t *testing.T) {
c := DefaultTestClient()
clearDatabase(t)
namespace := "onepanel"
workflowTemplate := &WorkflowTemplate{
Name: "test",
Manifest: defaultWorkflowTemplate,
}
created, _ := c.CreateWorkflowTemplate(namespace, workflowTemplate)
wt, err := c.GetWorkflowTemplate(namespace, created.UID, 0)
assert.NotNil(t, wt)
assert.Nil(t, err)
}
// testGetWorkflowTemplateNotFound attempts to get a not-found workflow template
func testClientGetWorkflowTemplateNotFound(t *testing.T) {
c := DefaultTestClient()
clearDatabase(t)
wt, err := c.GetWorkflowTemplate("onepanel", "uid-not-found", 0)
assert.Nil(t, wt)
userErr, ok := err.(*util.UserError)
assert.True(t, ok)
assert.Equal(t, codes.NotFound, userErr.Code)
}
func TestClient_GetWorkflowTemplate(t *testing.T) {
testClientGetWorkflowTemplateSuccess(t)
testClientGetWorkflowTemplateNotFound(t)
}

View File

@@ -10,9 +10,9 @@ import (
"github.com/onepanelio/core/pkg/util" "github.com/onepanelio/core/pkg/util"
"github.com/onepanelio/core/pkg/util/pagination" "github.com/onepanelio/core/pkg/util/pagination"
"github.com/onepanelio/core/pkg/util/ptr" "github.com/onepanelio/core/pkg/util/ptr"
uid2 "github.com/onepanelio/core/pkg/util/uid"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"strings"
"time" "time"
) )
@@ -33,16 +33,8 @@ func (c *Client) workspacesSelectBuilder(namespace string) sq.SelectBuilder {
return sb return sb
} }
func getWorkspaceParameterValue(parameters []Parameter, name string) *string { // mergeWorkspaceParameters combines two parameter arrays. If a parameter in newParameters is not in
for _, p := range parameters { // the existing ones, it is added. If it is, it is ignored.
if p.Name == name {
return p.Value
}
}
return nil
}
func mergeWorkspaceParameters(existingParameters, newParameters []Parameter) (parameters []Parameter) { func mergeWorkspaceParameters(existingParameters, newParameters []Parameter) (parameters []Parameter) {
parameterMap := make(map[string]*string, 0) parameterMap := make(map[string]*string, 0)
for _, p := range newParameters { for _, p := range newParameters {
@@ -73,10 +65,10 @@ func mergeWorkspaceParameters(existingParameters, newParameters []Parameter) (pa
// sys-workspace-action // sys-workspace-action
// sys-resource-action // sys-resource-action
// sys-host // sys-host
// TODO why does this generate the UID?
func injectWorkspaceSystemParameters(namespace string, workspace *Workspace, workspaceAction, resourceAction string, config SystemConfig) (err error) { func injectWorkspaceSystemParameters(namespace string, workspace *Workspace, workspaceAction, resourceAction string, config SystemConfig) (err error) {
workspace.UID, err = uid2.GenerateUID(workspace.Name, 30) if err := workspace.GenerateUID(workspace.Name); err != nil {
if err != nil { return err
return
} }
host := fmt.Sprintf("%v--%v.%v", workspace.UID, namespace, *config.Domain()) host := fmt.Sprintf("%v--%v.%v", workspace.UID, namespace, *config.Domain())
systemParameters := []Parameter{ systemParameters := []Parameter{
@@ -98,6 +90,10 @@ func injectWorkspaceSystemParameters(namespace string, workspace *Workspace, wor
return return
} }
// createWorkspace creates a workspace and related resources.
// The following are required on the workspace:
// WorkspaceTemplate.WorkflowTemplate.UID
// WorkspaceTemplate.WorkflowTemplate.Version
func (c *Client) createWorkspace(namespace string, parameters []byte, workspace *Workspace) (*Workspace, error) { func (c *Client) createWorkspace(namespace string, parameters []byte, workspace *Workspace) (*Workspace, error) {
systemConfig, err := c.GetSystemConfig() systemConfig, err := c.GetSystemConfig()
if err != nil { if err != nil {
@@ -155,7 +151,11 @@ func (c *Client) createWorkspace(namespace string, parameters []byte, workspace
QueryRow(). QueryRow().
Scan(&workspace.ID, &workspace.CreatedAt) Scan(&workspace.ID, &workspace.CreatedAt)
if err != nil { if err != nil {
return nil, util.NewUserErrorWrap(err, "Workspace") if strings.Contains(err.Error(), "invalid input syntax for type json") {
return nil, util.NewUserError(codes.InvalidArgument, err.Error())
}
return nil, util.NewUserError(codes.Unknown, err.Error())
} }
return workspace, nil return workspace, nil
@@ -182,7 +182,7 @@ func (c *Client) CreateWorkspace(namespace string, workspace *Workspace) (*Works
Value: ptr.String(workspace.UID), Value: ptr.String(workspace.UID),
}) })
sysHost := getWorkspaceParameterValue(workspace.Parameters, "sys-host") sysHost := workspace.GetParameterValue("sys-host")
if sysHost == nil { if sysHost == nil {
return nil, fmt.Errorf("sys-host parameter not found") return nil, fmt.Errorf("sys-host parameter not found")
} }

View File

@@ -719,15 +719,14 @@ metadata:
} }
func (c *Client) createWorkspaceTemplate(namespace string, workspaceTemplate *WorkspaceTemplate) (*WorkspaceTemplate, error) { func (c *Client) createWorkspaceTemplate(namespace string, workspaceTemplate *WorkspaceTemplate) (*WorkspaceTemplate, error) {
uid, err := uid2.GenerateUID(workspaceTemplate.Name, 30) err := workspaceTemplate.GenerateUID(workspaceTemplate.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
workspaceTemplate.UID = uid
workspaceTemplate.WorkflowTemplate.IsSystem = true workspaceTemplate.WorkflowTemplate.IsSystem = true
workspaceTemplate.WorkflowTemplate.Resource = ptr.String(TypeWorkspaceTemplate) workspaceTemplate.WorkflowTemplate.Resource = ptr.String(TypeWorkspaceTemplate)
workspaceTemplate.WorkflowTemplate.ResourceUID = ptr.String(uid) workspaceTemplate.WorkflowTemplate.ResourceUID = &workspaceTemplate.UID
// validate workflow template // validate workflow template
if err := c.validateWorkflowTemplate(namespace, workspaceTemplate.WorkflowTemplate); err != nil { if err := c.validateWorkflowTemplate(namespace, workspaceTemplate.WorkflowTemplate); err != nil {
@@ -754,7 +753,7 @@ func (c *Client) createWorkspaceTemplate(namespace string, workspaceTemplate *Wo
defer tx.Rollback() defer tx.Rollback()
err = sb.Insert("workspace_templates"). err = sb.Insert("workspace_templates").
SetMap(sq.Eq{ SetMap(sq.Eq{
"uid": uid, "uid": workspaceTemplate.UID,
"name": workspaceTemplate.Name, "name": workspaceTemplate.Name,
"namespace": namespace, "namespace": namespace,
"workflow_template_id": workspaceTemplate.WorkflowTemplate.ID, "workflow_template_id": workspaceTemplate.WorkflowTemplate.ID,

View File

@@ -52,6 +52,75 @@ routes:
workspaceTemplate = WorkspaceTemplate{ workspaceTemplate = WorkspaceTemplate{
Manifest: workspaceSpecManifest, Manifest: workspaceSpecManifest,
} }
jupyterLabWorkspaceManifest = `# Docker containers that are part of the Workspace
containers:
- name: jupyterlab-tensorflow
image: jupyter/tensorflow-notebook
command: [start.sh, jupyter]
workingDir: /data
env:
- name: tornado
value: "{ 'headers': { 'Content-Security-Policy': \"frame-ancestors * 'self'\" } }"
- name: GRANT_SUDO
value: 1
- name: CHOWN_EXTRA
value: '/data'
- name: CHOWN_EXTRA_OPTS
value: '-R'
securityContext:
runAsUser: 0
allowPrivilegeEscalation: false
args:
- lab
- --LabApp.token=''
- --LabApp.allow_remote_access=True
- --LabApp.allow_origin="*"
- --LabApp.disable_check_xsrf=True
- --LabApp.trust_xheaders=True
- --LabApp.tornado_settings=$(tornado)
- --NotebookApp.notebook_dir='/data'
ports:
- containerPort: 8888
name: jupyterlab
# Volumes to be mounted in this container
# Onepanel will automatically create these volumes and mount them to the container
volumeMounts:
- name: data
mountPath: /data
# Ports that need to be exposed
ports:
- name: jupyterlab
port: 80
protocol: TCP
targetPort: 8888
# Routes that will map to ports
routes:
- match:
- uri:
prefix: /
route:
- destination:
port:
number: 80
# DAG Workflow to be executed once a Workspace action completes
# postExecutionWorkflow:
# entrypoint: main
# templates:
# - name: main
# dag:
# tasks:
# - name: slack-notify
# template: slack-notify
# - name: slack-notify
# container:
# image: technosophos/slack-notify
# args:
# - SLACK_USERNAME=onepanel SLACK_TITLE="Your workspace is ready" SLACK_ICON=https://www.gravatar.com/avatar/5c4478592fe00878f62f0027be59c1bd SLACK_MESSAGE="Your workspace is now running" ./slack-notify
# command:
# - sh
# - -c
`
) )
func TestParseWorkspaceSpec(t *testing.T) { func TestParseWorkspaceSpec(t *testing.T) {
@@ -65,3 +134,44 @@ func TestParseWorkspaceSpec(t *testing.T) {
assert.Equal(t, workspaceSpec.Containers[0].Ports[0].ContainerPort, int32(80)) assert.Equal(t, workspaceSpec.Containers[0].Ports[0].ContainerPort, int32(80))
assert.Equal(t, workspaceSpec.Containers[1].Ports[0].ContainerPort, int32(443)) assert.Equal(t, workspaceSpec.Containers[1].Ports[0].ContainerPort, int32(443))
} }
// testClientCreateWorkspaceTemplateNew creates a new workspace template
func testClientCreateWorkspaceTemplateNew(t *testing.T) {
c := DefaultTestClient()
clearDatabase(t)
namespace := "onepanel"
wt := &WorkspaceTemplate{
Name: "test",
Manifest: jupyterLabWorkspaceManifest,
}
_, err := c.CreateWorkspaceTemplate(namespace, wt)
assert.Nil(t, err)
}
// testClientCreateWorkspaceTemplateDuplicateName attempts to create a workspace template for a name that already exists
// this should error
func testClientCreateWorkspaceTemplateDuplicateName(t *testing.T) {
c := DefaultTestClient()
clearDatabase(t)
namespace := "onepanel"
wt := &WorkspaceTemplate{
Name: "test",
Manifest: jupyterLabWorkspaceManifest,
}
_, err := c.CreateWorkspaceTemplate(namespace, wt)
_, err = c.CreateWorkspaceTemplate(namespace, wt)
assert.NotNil(t, err)
}
func TestClient_CreateWorkspaceTemplate(t *testing.T) {
testClientCreateWorkspaceTemplateNew(t)
testClientCreateWorkspaceTemplateDuplicateName(t)
}

View File

@@ -3,6 +3,7 @@ package v1
import ( import (
"fmt" "fmt"
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1" wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
uid2 "github.com/onepanelio/core/pkg/util/uid"
"github.com/onepanelio/core/util/sql" "github.com/onepanelio/core/util/sql"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"time" "time"
@@ -25,6 +26,18 @@ type WorkspaceTemplate struct {
WorkflowTemplateID uint64 `db:"workflow_template_id"` WorkflowTemplateID uint64 `db:"workflow_template_id"`
} }
// GenerateUID generates a uid from the input name and sets it on the workflow template
func (wt *WorkspaceTemplate) GenerateUID(name string) error {
result, err := uid2.GenerateUID(name, 30)
if err != nil {
return err
}
wt.UID = result
return nil
}
// InjectRuntimeParameters will inject all runtime variables into the WorkflowTemplate's manifest. // InjectRuntimeParameters will inject all runtime variables into the WorkflowTemplate's manifest.
func (wt *WorkspaceTemplate) InjectRuntimeParameters(config SystemConfig) error { func (wt *WorkspaceTemplate) InjectRuntimeParameters(config SystemConfig) error {
if wt.WorkflowTemplate == nil { if wt.WorkflowTemplate == nil {

View File

@@ -1 +1,74 @@
package v1 package v1
import (
"github.com/onepanelio/core/pkg/util"
"github.com/onepanelio/core/pkg/util/ptr"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"testing"
)
// testClientPrivateCreateWorkspaceNoWorkflowTemplate makes sure we get an error when there is no workflow template for the workspace
func testClientPrivateCreateWorkspaceNoWorkflowTemplate(t *testing.T) {
c := DefaultTestClient()
clearDatabase(t)
namespace := "onepanel"
workspace := &Workspace{
Name: "test",
WorkspaceTemplate: &WorkspaceTemplate{
WorkflowTemplate: &WorkflowTemplate{
UID: "not-exist",
Version: 1,
},
},
}
workspace.GenerateUID("test")
_, err := c.createWorkspace(namespace, []byte(""), workspace)
userErr, ok := err.(*util.UserError)
assert.True(t, ok)
assert.Equal(t, userErr.Code, codes.NotFound)
}
// testClientPrivateCreateWorkspaceSuccess tests creating a workspace successfully
func testClientPrivateCreateWorkspaceSuccess(t *testing.T) {
c := DefaultTestClient()
clearDatabase(t)
namespace := "onepanel"
workspaceTemplate := &WorkspaceTemplate{
Name: "test",
Manifest: jupyterLabWorkspaceManifest,
}
workspaceTemplate, _ = c.CreateWorkspaceTemplate(namespace, workspaceTemplate)
workspace := &Workspace{
Name: "test2",
WorkspaceTemplate: workspaceTemplate,
Parameters: []Parameter{
{
Name: "workflow-execution-name",
Value: ptr.String("test2"),
},
},
}
workspace.GenerateUID("test")
_, err := c.createWorkspace(namespace, []byte("{}"), workspace)
assert.Nil(t, err)
}
func TestClient_createWorkspace(t *testing.T) {
testClientPrivateCreateWorkspaceNoWorkflowTemplate(t)
testClientPrivateCreateWorkspaceSuccess(t)
}
func TestClient_CreateWorkspace(t *testing.T) {
}

View File

@@ -3,6 +3,7 @@ package v1
import ( import (
"fmt" "fmt"
wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1" wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
uid2 "github.com/onepanelio/core/pkg/util/uid"
"github.com/onepanelio/core/util/sql" "github.com/onepanelio/core/util/sql"
networking "istio.io/api/networking/v1alpha3" networking "istio.io/api/networking/v1alpha3"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@@ -63,6 +64,29 @@ func (w *Workspace) GetURL(protocol, domain string) string {
return fmt.Sprintf("%v%v--%v.%v", protocol, w.UID, w.Namespace, domain) return fmt.Sprintf("%v%v--%v.%v", protocol, w.UID, w.Namespace, domain)
} }
// GetParameterValue returns the value of the parameter with the given name, or nil if there is no such parameter
func (w *Workspace) GetParameterValue(name string) *string {
for _, p := range w.Parameters {
if p.Name == name {
return p.Value
}
}
return nil
}
// GenerateUID generates a uid from the input name and sets it on the workspace
func (w *Workspace) GenerateUID(name string) error {
result, err := uid2.GenerateUID(name, 30)
if err != nil {
return err
}
w.UID = result
return nil
}
// getWorkspaceColumns returns all of the columns for workspace modified by alias, destination. // getWorkspaceColumns returns all of the columns for workspace modified by alias, destination.
// see formatColumnSelect // see formatColumnSelect
func getWorkspaceColumns(aliasAndDestination ...string) []string { func getWorkspaceColumns(aliasAndDestination ...string) []string {