Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ip) - add ovh_ip_move resource to move an IP to a provided service name or park it if no service name given #510

Merged
merged 5 commits into from
Mar 14, 2024

Conversation

julianguinard
Copy link
Contributor

@julianguinard julianguinard commented Dec 5, 2023

Description

This adds a new add ovh_ip_move resource to move an IP to a provided service name or park it if empty service is given.

It relies on the following APIV6 routes:

taken resource ID is the move task ID if available, or the IP itself otherwise

Moving/parking process:

  • if there is an IpTask recorded in the resource state, we try to wait for it to be finished until it is considered expired
  • after having waited until previous task expiration or completion, do the move only if the current service is different from the one given in the inputs

Type of change

  • New feature (non-breaking change which adds functionality)
  • Documentation update

How Has This Been Tested?

  • Test :
make testacc TESTARGS="-run TestAccIpMove_basic"`
=== RUN   TestAccIpMove_basic
--- PASS: TestAccIpMove_basic (108.21s)
PASS

Test Configuration:

  • Terraform version: terraform version: Terraform v1.2.9
  • Existing HCL configuration you used:

Attaching an IP to a service

resource "ovh_ip_move" "move" {
    ip = "X.X.X.X/32"
    routed_to {
      service_name = "loadbalancer-YYYYYYYY"
    }
}

Then parking the same IP

resource "ovh_ip_move" "move" {
    ip = "X.X.X.X/32"
    routed_to {
      service_name = ""
    }
}

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added acceptance tests that prove my fix is effective or that my feature works
  • New and existing acceptance tests pass locally with my changes

@julianguinard julianguinard changed the title add ovh_ip_move resource to move an IP to a provided service name or park it if no service name given feat(ip) - add ovh_ip_move resource to move an IP to a provided service name or park it if no service name given Dec 5, 2023
@yomovh yomovh requested a review from rbeuque74 December 6, 2023 08:24
@Stoakes
Copy link
Member

Stoakes commented Dec 6, 2023

Personal opinion, but I feel that naming could be improved. Move is an action verb. It doesn't feel right once it is applied.

What about something like ovh_ip_association ? Disclaimer, 100% inspired by aws_eip_association

@scraly
Copy link
Collaborator

scraly commented Dec 6, 2023

The naming of the resource and datasource depends of the API endpoint you call :-)

endpoint := fmt.Sprintf("/ip/%s/move",

So, depending of the API scheme, ovh_ip_move is a good name.
If we want another name, we have to think it when we create the API endpoint :).

const taskExpiresAfter = 300 * time.Second

// WaitingTimeInSecondsBeforeRefreshState number if seconds to wait before making a new API call to refresh ip task state
const WaitingTimeInSecondsBeforeRefreshState = 10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const WaitingTimeInSecondsBeforeRefreshState = 10
const waitingTimeInSecondsBeforeRefreshState = 10

I think it doesn't need to be exported

config := meta.(*Config)
ip := d.Get("ip").(string)

// no need to update if ip is already routed to the appropriate service
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment does not seem related to the code ? (from what I understand FromResource func doesn't check that)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, check is done later down the road.
will move comment to l163


endpoint := fmt.Sprintf("/ip/%s/task/%d",
url.PathEscape(d.Get("ip").(string)),
ipTask.TaskId,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs to be url.PathEscape'd

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but ipTask.TaskId is an int64, not a string (see %d formatter)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my bad, read too fast :)

if timeDiff < taskExpiresAfter {
log.Printf("[DEBUG] ipTask.Status is currently: %s. Waiting %d second (we allow %f more seconds for the task to complete)", ipTask.Status, WaitingTimeInSecondsBeforeRefreshState, taskExpiresAfter.Seconds()-timeDiff.Seconds())
time.Sleep(WaitingTimeInSecondsBeforeRefreshState * time.Second)
err = resourceIpTaskRead(d, ipTask, meta)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this error should be checked and we should return immediately if it's not nil: if the task doesn't exist anymore for example, I think we shouldn't retry.

What do you think ?

Copy link
Contributor Author

@julianguinard julianguinard Mar 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in some instances such as a gone task it might make sense to indeed return error immediately. We can possibly do this by verifying ipTask nillity at the very beginning of recursiveWaitTaskFinished, which would achieve a similar behavior

In other cases though (network error, temporary server error, etc...) stopping the whole plan is not relevant nor wanted, and we would rather retry recursively

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems ok as a workaround, but IMO a better solution is to verify the error response like it's done in func resourceIpServiceReadByServiceName (with type assertion err.(*ovh.APIError) + status check)

func waitForTaskFinished(d *schema.ResourceData, meta interface{}, ipTask *IpTask, ip string, opts *IpMoveOpts) (finishedWithSuccess *bool, err error) {
defer func() {
errSet := d.Set("task_status", ipTask.Status)
if errSet == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't it be != nil instead ? Otherwise you will hide the underlying error, for example in case your task has a status IpTaskStatusOvhError.

Additionally, it would be preferable to avoid this defer to ease readability :)

Copy link
Contributor Author

@julianguinard julianguinard Mar 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will replace the whole method body to the following as I realize that go 1.20 introduced a way of joining errors, which is actually what I was ultimately willing to achieve so we are aware of both potential Set error and/or recursiveWaitTaskFinished() error

finishedWithSuccess, err = recursiveWaitTaskFinished(d, meta, ipTask, ip, opts)
errSet := d.Set("task_status", ipTask.Status)

return finishedWithSuccess, errors.Join(err, errSet)

* `organisation_id` - IP block organisation Id
* `routed_to` - Routage information
* `service_name` - Service where ip is routed to
* `service_name`: service name in the form of `ip-<part-1>.<part-2>.<part-3>.<part-4>`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `service_name`: service name in the form of `ip-<part-1>.<part-2>.<part-3>.<part-4>`
* `service_name`: Service name in the form of `ip-<part-1>.<part-2>.<part-3>.<part-4>`

* `can_be_terminated` - can be terminated
* `country` - country
* `description` - description attached to the IP
* `ip` - ip block
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `ip` - ip block
* `ip` - IP block


* `can_be_terminated` - can be terminated
* `country` - country
* `description` - description attached to the IP
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `description` - description attached to the IP
* `description` - Description attached to the IP

Attributes are mostly the same as for [ovh_ip_service](https://registry.terraform.io/providers/ovh/ovh/latest/docs/resources/ip_service#attributes-reference):

* `can_be_terminated` - can be terminated
* `country` - country
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `country` - country
* `country` - Country


Attributes are mostly the same as for [ovh_ip_service](https://registry.terraform.io/providers/ovh/ovh/latest/docs/resources/ip_service#attributes-reference):

* `can_be_terminated` - can be terminated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `can_be_terminated` - can be terminated
* `can_be_terminated` - Whether IP service can be terminated

@julianguinard julianguinard force-pushed the add_ovh_ip_move branch 2 times, most recently from a9ff432 to 5e4f2b9 Compare March 12, 2024 16:28
if timeDiff < taskExpiresAfter {
log.Printf("[DEBUG] ipTask.Status is currently: %s. Waiting %d second (we allow %f more seconds for the task to complete)", ipTask.Status, WaitingTimeInSecondsBeforeRefreshState, taskExpiresAfter.Seconds()-timeDiff.Seconds())
time.Sleep(WaitingTimeInSecondsBeforeRefreshState * time.Second)
err = resourceIpTaskRead(d, ipTask, meta)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems ok as a workaround, but IMO a better solution is to verify the error response like it's done in func resourceIpServiceReadByServiceName (with type assertion err.(*ovh.APIError) + status check)

ovh/resource_ip_move.go Outdated Show resolved Hide resolved
@amstuta
Copy link
Contributor

amstuta commented Mar 13, 2024

final request, can you add a pipeline in https://github.com/ovh/terraform-provider-ovh/blob/master/.cds/terraform-provider-ovh.yml ? So that your test is run in the CI

julian GUINARD added 3 commits March 13, 2024 14:35
…park it if no service name given

taken resource ID is the move task ID if available, or the IP itself otherwise

Moving/parking process:
- if there is an IpTask recorded in the resource state, we try to wait for it to be finished until it is considered expired
- after having waited until previosu task expiration or completion, do the move only if the current service is different from the one given in the inputs

add acceptance test that checks attaching IP to a service then detaching it
@amstuta amstuta merged commit 2c5aff2 into ovh:master Mar 14, 2024
@julianguinard julianguinard deleted the add_ovh_ip_move branch September 23, 2024 08:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants