Skip to content

Commit

Permalink
validate for version compatability with updated cluster
Browse files Browse the repository at this point in the history
  • Loading branch information
furkhat committed Jul 19, 2021
1 parent 781a814 commit fc3fb5b
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 87 deletions.
113 changes: 104 additions & 9 deletions metakube/resource_metakube_node_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package metakube
import (
"context"
"fmt"
"github.com/hashicorp/go-version"
"github.com/syseleven/go-metakube/client/versions"
"net/http"
"strings"
"time"
Expand Down Expand Up @@ -118,14 +120,21 @@ func metakubeResourceNodeDeploymentCreate(ctx context.Context, d *schema.Resourc
return diag.Errorf("")
}
}

nodeDeployment := &models.NodeDeployment{
Name: d.Get("name").(string),
Spec: metakubeNodeDeploymentExpandSpec(d.Get("spec").([]interface{})),
}

if err := metakubeResourceNodeDeploymentVersionCompatibleWithCluster(ctx, k, projectID, clusterID, nodeDeployment); err != nil {
return diag.FromErr(err)
}

p := project.NewCreateMachineDeploymentParams().
WithContext(ctx).
WithProjectID(projectID).
WithClusterID(clusterID).
WithBody(&models.NodeDeployment{
Name: d.Get("name").(string),
Spec: metakubeNodeDeploymentExpandSpec(d.Get("spec").([]interface{})),
})
WithBody(nodeDeployment)

if err := metakubeResourceClusterWaitForReady(ctx, k, d, projectID, clusterID); err != nil {
return diag.Errorf("cluster is not ready: %v", err)
Expand Down Expand Up @@ -207,15 +216,21 @@ func metakubeResourceNodeDeploymentUpdate(ctx context.Context, d *schema.Resourc
k := m.(*metakubeProviderMeta)
projectID := d.Get("project_id").(string)
clusterID := d.Get("cluster_id").(string)

nodeDeployment := &models.NodeDeployment{
Spec: metakubeNodeDeploymentExpandSpec(d.Get("spec").([]interface{})),
}

if err := metakubeResourceNodeDeploymentVersionCompatibleWithCluster(ctx, k, projectID, clusterID, nodeDeployment); err != nil {
return diag.FromErr(err)
}

p := project.NewPatchMachineDeploymentParams()
p.SetContext(ctx)
p.SetProjectID(projectID)
p.SetClusterID(clusterID)
p.SetMachineDeploymentID(d.Id())
p.SetPatch(&models.NodeDeployment{
Spec: metakubeNodeDeploymentExpandSpec(d.Get("spec").([]interface{})),
})

p.SetPatch(nodeDeployment)
res, err := k.client.Project.PatchMachineDeployment(p, k.auth)
if err != nil {
return diag.Errorf("unable to update a node deployment: %v", stringifyResponseError(err))
Expand Down Expand Up @@ -266,7 +281,16 @@ func metakubeResourceNodeDeploymentUpdate(ctx context.Context, d *schema.Resourc
p.SetMachineDeploymentID(d.Id())
p.SetPatch(&patch)

_, err := k.client.Project.PatchMachineDeployment(p, k.auth)
resource.RetryContext(ctx, d.Timeout(schema.TimeoutUpdate), func() *resource.RetryError {
_, err := k.client.Project.PatchMachineDeployment(p, k.auth)
if err != nil {
if strings.Contains(stringifyResponseError(err), "the object has been modified") {
return resource.RetryableError(fmt.Errorf("machine deployment patch conflict: %v", err))
}
return resource.NonRetryableError(fmt.Errorf("patch machine deployment '%s': %v", d.Id(), err))
}
return nil
})
if err != nil {
return diag.Errorf("unable to update a node deployment: %v", stringifyResponseError(err))
}
Expand All @@ -280,6 +304,77 @@ func metakubeResourceNodeDeploymentUpdate(ctx context.Context, d *schema.Resourc
return metakubeResourceNodeDeploymentRead(ctx, d, m)
}

func metakubeResourceNodeDeploymentVersionCompatibleWithCluster(ctx context.Context, k *metakubeProviderMeta, projectID, clusterID string, ndepl *models.NodeDeployment) error {
cluster, err := metakubeGetCluster(ctx, projectID, clusterID, k)
if err != nil {
return err
}
clusterVersion := cluster.Spec.Version.(string)

var kubeletVersion string
if ndepl.Spec.Template != nil && ndepl.Spec.Template.Versions != nil {
kubeletVersion = ndepl.Spec.Template.Versions.Kubelet
}
err = validateVersionAgainstCluster(kubeletVersion, clusterVersion)
if err != nil {
return err
}
return validateKubeletVersionIsAvailable(k, kubeletVersion, clusterVersion)
}

func validateVersionAgainstCluster(kubeletVersion, clusterVersion string) error {
if kubeletVersion == "" {
return nil
}

clusterSemverVersion, err := version.NewVersion(clusterVersion)
if err != nil {
return err
}

v, err := version.NewVersion(kubeletVersion)

if err != nil {
return fmt.Errorf("unable to parse node deployment version")
}

if clusterSemverVersion.LessThan(v) {
return fmt.Errorf("node deployment version (%s) cannot be greater than cluster version (%s)", v, clusterVersion)
}
return nil
}

func validateKubeletVersionIsAvailable(k *metakubeProviderMeta, kubeletVersion, clusterVersion string) error {
if kubeletVersion == "" {
return nil
}

versionType := "kubernetes"

p := versions.NewGetNodeUpgradesParams()
p.SetType(&versionType)
p.SetControlPlaneVersion(&clusterVersion)
r, err := k.client.Versions.GetNodeUpgrades(p, k.auth)

if err != nil {
if e, ok := err.(*versions.GetNodeUpgradesDefault); ok && e.Payload != nil && e.Payload.Error != nil && e.Payload.Error.Message != nil {
return fmt.Errorf("get node_deployment upgrades: %s", *e.Payload.Error.Message)
}
return err
}

var availableVersions []string
for _, v := range r.Payload {
s, ok := v.Version.(string)
if ok && s == kubeletVersion && !v.RestrictedByKubeletVersion {
return nil
}
availableVersions = append(availableVersions, s)
}

return fmt.Errorf("unknown version for node deployment %s, available versions %v", kubeletVersion, availableVersions)
}

func metakubeResourceNodeDeploymentWaitForReady(ctx context.Context, k *metakubeProviderMeta, timeout time.Duration, projectID, clusterID, id string, generation int64) error {
return resource.RetryContext(ctx, timeout, func() *resource.RetryError {
p := project.NewGetMachineDeploymentParams().
Expand Down
18 changes: 9 additions & 9 deletions metakube/resource_metakube_node_deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ func TestAccMetakubeNodeDeployment_Openstack_Basic(t *testing.T) {
image := os.Getenv(testEnvOpenstackImage)
image2 := os.Getenv(testEnvOpenstackImage2)
flavor := os.Getenv(testEnvOpenstackFlavor)
k8sVersion17 := os.Getenv(testEnvK8sVersion)
kubeletVersion16 := os.Getenv(testEnvK8sOlderVersion)
k8sVersionNew := os.Getenv(testEnvK8sVersion)
k8sVersionOld := os.Getenv(testEnvK8sOlderVersion)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheckForOpenstack(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckMetaKubeNodeDeploymentDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckMetaKubeNodeDeploymentBasic(testName, nodeDC, username, password, tenant, k8sVersion17, kubeletVersion16, image, flavor),
Config: testAccCheckMetaKubeNodeDeploymentBasic(testName, nodeDC, username, password, tenant, k8sVersionOld, k8sVersionOld, image, flavor),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckMetaKubeNodeDeploymentExists(resourceName, &ndepl),
testAccCheckMetaKubeNodeDeploymentFields(&ndepl, flavor, image, kubeletVersion16, 1, 0, false),
testAccCheckMetaKubeNodeDeploymentFields(&ndepl, flavor, image, k8sVersionOld, 1, 0, false),
resource.TestCheckResourceAttr(resourceName, "name", testName),
resource.TestCheckResourceAttrPtr(resourceName, "name", &ndepl.Name),
resource.TestCheckResourceAttr(resourceName, "spec.0.replicas", "1"),
Expand All @@ -46,12 +46,12 @@ func TestAccMetakubeNodeDeployment_Openstack_Basic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.cloud.0.openstack.0.flavor", flavor),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.cloud.0.openstack.0.image", image),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.operating_system.0.ubuntu.#", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.versions.0.kubelet", kubeletVersion16),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.versions.0.kubelet", k8sVersionOld),
resource.TestCheckResourceAttr(resourceName, "spec.0.dynamic_config", "false"),
),
},
{
Config: testAccCheckMetaKubeNodeDeploymentBasic2(testName, nodeDC, username, password, tenant, k8sVersion17, kubeletVersion16, image2, flavor),
Config: testAccCheckMetaKubeNodeDeploymentBasic2(testName, nodeDC, username, password, tenant, k8sVersionNew, k8sVersionNew, image2, flavor),
Check: resource.ComposeAggregateTestCheckFunc(
testResourceInstanceState(resourceName, func(is *terraform.InstanceState) error {
// Record IDs to test import
Expand All @@ -61,7 +61,7 @@ func TestAccMetakubeNodeDeployment_Openstack_Basic(t *testing.T) {
return nil
}),
testAccCheckMetaKubeNodeDeploymentExists(resourceName, &ndepl),
testAccCheckMetaKubeNodeDeploymentFields(&ndepl, flavor, image2, kubeletVersion16, 1, 123, true),
testAccCheckMetaKubeNodeDeploymentFields(&ndepl, flavor, image2, k8sVersionNew, 1, 123, true),
resource.TestCheckResourceAttr(resourceName, "name", testName),
resource.TestCheckResourceAttr(resourceName, "spec.0.replicas", "1"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.labels.%", "3"),
Expand All @@ -71,12 +71,12 @@ func TestAccMetakubeNodeDeployment_Openstack_Basic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.cloud.0.openstack.0.use_floating_ip", "true"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.cloud.0.openstack.0.disk_size", "123"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.operating_system.0.ubuntu.0.dist_upgrade_on_boot", "true"),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.versions.0.kubelet", kubeletVersion16),
resource.TestCheckResourceAttr(resourceName, "spec.0.template.0.versions.0.kubelet", k8sVersionNew),
resource.TestCheckResourceAttr(resourceName, "spec.0.dynamic_config", "true"),
),
},
{
Config: testAccCheckMetaKubeNodeDeploymentBasic2(testName, nodeDC, username, password, tenant, k8sVersion17, kubeletVersion16, image2, flavor),
Config: testAccCheckMetaKubeNodeDeploymentBasic2(testName, nodeDC, username, password, tenant, k8sVersionNew, k8sVersionNew, image2, flavor),
PlanOnly: true,
},
{
Expand Down
72 changes: 3 additions & 69 deletions metakube/resource_metakube_node_deployment_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import (
"context"
"fmt"

version "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/syseleven/go-metakube/client/project"
"github.com/syseleven/go-metakube/client/versions"
"github.com/syseleven/go-metakube/models"
)

Expand All @@ -22,13 +20,7 @@ func validateNodeSpecMatchesCluster() schema.CustomizeDiffFunc {
if projectID == "" {
return nil
}
cluster, err := metakubeGetCluster(projectID, clusterID, k)
if err != nil {
return err
}
clusterVersion := cluster.Spec.Version.(string)

err = validateVersionAgainstCluster(d, clusterVersion)
cluster, err := metakubeGetCluster(ctx, projectID, clusterID, k)
if err != nil {
return err
}
Expand All @@ -40,10 +32,6 @@ func validateNodeSpecMatchesCluster() schema.CustomizeDiffFunc {
if err != nil {
return err
}
err = validateKubeletVersionIsAvailable(d, k, clusterVersion)
if err != nil {
return err
}
return nil
}
}
Expand Down Expand Up @@ -83,63 +71,9 @@ func validateProviderMatchesCluster(d *schema.ResourceDiff, clusterProvider stri

}

func validateVersionAgainstCluster(d *schema.ResourceDiff, clusterVersion string) error {
nodeVersion, ok := d.Get("spec.0.template.0.versions.0.kubelet").(string)
if nodeVersion == "" || !ok {
return nil
}

clusterSemverVersion, err := version.NewVersion(clusterVersion)
if err != nil {
return err
}

v, err := version.NewVersion(nodeVersion)

if err != nil {
return fmt.Errorf("unable to parse node deployment version")
}

if clusterSemverVersion.LessThan(v) {
return fmt.Errorf("node deployment version (%s) cannot be greater than cluster version (%s)", v, clusterVersion)
}
return nil
}

func validateKubeletVersionIsAvailable(d *schema.ResourceDiff, k *metakubeProviderMeta, clusterVersion string) error {
version := d.Get("spec.0.template.0.versions.0.kubelet").(string)
if version == "" || version == clusterVersion {
return nil
}

versionType := "kubernetes"

p := versions.NewGetNodeUpgradesParams()
p.SetType(&versionType)
p.SetControlPlaneVersion(&clusterVersion)
r, err := k.client.Versions.GetNodeUpgrades(p, k.auth)

if err != nil {
if e, ok := err.(*versions.GetNodeUpgradesDefault); ok && e.Payload != nil && e.Payload.Error != nil && e.Payload.Error.Message != nil {
return fmt.Errorf("get node_deployment upgrades: %s", *e.Payload.Error.Message)
}
return err
}

var availableVersions []string
for _, v := range r.Payload {
s, ok := v.Version.(string)
if ok && s == version && !v.RestrictedByKubeletVersion {
return nil
}
availableVersions = append(availableVersions, s)
}

return fmt.Errorf("unknown version for node deployment %s, available versions %v", version, availableVersions)
}

func metakubeGetCluster(proj, cls string, k *metakubeProviderMeta) (*models.Cluster, error) {
func metakubeGetCluster(ctx context.Context, proj, cls string, k *metakubeProviderMeta) (*models.Cluster, error) {
p := project.NewGetClusterV2Params().
WithContext(ctx).
WithProjectID(proj).
WithClusterID(cls)
r, err := k.client.Project.GetClusterV2(p, k.auth)
Expand Down

0 comments on commit fc3fb5b

Please sign in to comment.