Repository: kubernetes/cloud-provider-aws
Pull request #131 (2020-09-25 22:51:11)

instances: initial implementation of instancesV2 interface
<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. For reference on required PR/issue labels, read here:
https://git.k8s.io/community/contributors/devel/sig-release/release.md#issue-kind-label
3. Ensure you have added or ran the appropriate tests for your PR: https://git.k8s.io/community/contributors/devel/sig-testing/testing.md
4. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
5. Follow the instructions for writing a release note: https://git.k8s.io/community/contributors/guide/release-notes.md
6. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

**What type of PR is this?**
/kind feature

**What this PR does / why we need it**:
Take over the initial work here: https://github.com/kubernetes/cloud-provider-aws/pull/127
This is a first pass at implementing Instances. 
Some TODOs:
- add more unit tests
- support node naming policy other than private DNS names

**Which issue(s) this PR fixes**:
<!-- 
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Part of #125

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:
<!--  
If no, just write "NONE".
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
2. 
-->
```release-note
Add initial implementation of Instances for the v2 provider.
```


k8s-ci-robot: Hi @nicolehanjing. Thanks for your PR.

I'm waiting for a [kubernetes](https://github.com/orgs/kubernetes/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fcloud-provider-aws).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/cloud-provider-aws/pull/131#" title="Author self-approved">nicolehanjing</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **leakingtapan**
You can assign the PR to them by writing `/assign @leakingtapan` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fcloud-provider-aws).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes/cloud-provider-aws/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["leakingtapan"]} -->
randomvariable: We'll need some follow up issues to do the following, but not blocking this PR:
* Add rate limiters and metrics collection to the AWS request handlers
* Check if there's anything needed to support custom EC2 endpoints (for airgapped and Outposts setups)
randomvariable: /ok-to-test
andrewsykim: > We'll need some follow up issues to do the following, but not blocking this PR:
>   Add rate limiters and metrics collection to the AWS request handlers
>   Check if there's anything needed to support custom EC2 endpoints (for airgapped and Outposts setups)

+1, we should definitely add the first one before removing the alpha gating env var:

https://github.com/kubernetes/cloud-provider-aws/blob/4eef54c572faaa38bfeeef357c59489af8781661/cmd/aws-cloud-controller-manager/main.go#L104-L108

nicolehanjing: /retest
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/cloud-provider-aws/pull/131#" title="Author self-approved">nicolehanjing</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **leakingtapan** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @leakingtapan` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fcloud-provider-aws).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes/cloud-provider-aws/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["leakingtapan"]} -->
k8s-ci-robot: @nicolehanjing: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cloud-provider-aws-check | c7bd4fe468d7f56f315c7d39e35e4df4b4d8331b | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-check/1311909134462881792) | `/test pull-cloud-provider-aws-check`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=cloud-provider-aws&pr=131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anicolehanjing). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/cloud-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @nicolehanjing: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cloud-provider-aws-check | 090500e5cc412a4c382672078d562847f16d65f9 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-check/1312129274219597824) | `/test pull-cloud-provider-aws-check`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=cloud-provider-aws&pr=131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anicolehanjing). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/cloud-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @nicolehanjing: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cloud-provider-aws-check | 788d5fe334947c466a5475dfe3ca8e9a8c570d6a | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-check/1312130081233047552) | `/test pull-cloud-provider-aws-check`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=cloud-provider-aws&pr=131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anicolehanjing). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/cloud-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
nicolehanjing: /test pull-cloud-provider-aws-check
k8s-ci-robot: @nicolehanjing: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cloud-provider-aws-check | 788d5fe334947c466a5475dfe3ca8e9a8c570d6a | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-check/1312131018764849152) | `/test pull-cloud-provider-aws-check`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=cloud-provider-aws&pr=131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anicolehanjing). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/cloud-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
nicolehanjing: /retest
k8s-ci-robot: @nicolehanjing: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cloud-provider-aws-test | 7a795416eed92de983660203e1471fece276b63c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-test/1313992560015839232) | `/test pull-cloud-provider-aws-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=cloud-provider-aws&pr=131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anicolehanjing). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/cloud-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @nicolehanjing: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cloud-provider-aws-test | 14143a6abf1677bbf5a8a328018269a4b89d5ca1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-test/1313994800520761344) | `/test pull-cloud-provider-aws-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=cloud-provider-aws&pr=131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anicolehanjing). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/cloud-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @nicolehanjing: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cloud-provider-aws-test | 14143a6abf1677bbf5a8a328018269a4b89d5ca1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-test/1313994800520761344) | `/test pull-cloud-provider-aws-test`
pull-cloud-provider-aws-check | 86a6f09ea53c0575d88d7f45998ed08d016d76c5 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-check/1314079493756817408) | `/test pull-cloud-provider-aws-check`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=cloud-provider-aws&pr=131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anicolehanjing). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/cloud-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @nicolehanjing: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cloud-provider-aws-check | 86a6f09ea53c0575d88d7f45998ed08d016d76c5 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-check/1314079493756817408) | `/test pull-cloud-provider-aws-check`
pull-cloud-provider-aws-test | 86a6f09ea53c0575d88d7f45998ed08d016d76c5 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-test/1314079493781983232) | `/test pull-cloud-provider-aws-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=cloud-provider-aws&pr=131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anicolehanjing). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/cloud-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
nicolehanjing: /retest
k8s-ci-robot: @nicolehanjing: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cloud-provider-aws-test | 86a6f09ea53c0575d88d7f45998ed08d016d76c5 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-test/1314079493781983232) | `/test pull-cloud-provider-aws-test`
pull-cloud-provider-aws-check | 91fae076cc43fe9c6b23153643df0b047bcf88fa | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-check/1314257970778869760) | `/test pull-cloud-provider-aws-check`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=cloud-provider-aws&pr=131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anicolehanjing). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/cloud-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
andrewsykim: @nckturner mind giving this a review? This is just a first pass with base functionality, we still need to discuss naming policy, tagging, etc but I think we can do this iteratively in a follow-up PR.
nckturner: /cc @micahhausler @wongma7 
k8s-ci-robot: @nicolehanjing: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cloud-provider-aws-check | 1c1ac061a9374a1a5d98d56e9fa2f31fba3bd93b | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-check/1318340541766701056) | `/test pull-cloud-provider-aws-check`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=cloud-provider-aws&pr=131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anicolehanjing). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/cloud-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
nicolehanjing: /retest
k8s-ci-robot: @nicolehanjing: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cloud-provider-aws-check | 1c1ac061a9374a1a5d98d56e9fa2f31fba3bd93b | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-check/1318341893255335936) | `/test pull-cloud-provider-aws-check`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=cloud-provider-aws&pr=131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anicolehanjing). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/cloud-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @nicolehanjing: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cloud-provider-aws-check | ccc5ee3865a07a7ad759c52d43607b03b425028e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-check/1318397856318492672) | `/test pull-cloud-provider-aws-check`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=cloud-provider-aws&pr=131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anicolehanjing). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/cloud-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @nicolehanjing: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cloud-provider-aws-check | ccc5ee3865a07a7ad759c52d43607b03b425028e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-check/1318397856318492672) | `/test pull-cloud-provider-aws-check`
pull-cloud-provider-aws-test | ccc5ee3865a07a7ad759c52d43607b03b425028e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/cloud-provider-aws/131/pull-cloud-provider-aws-test/1318397856343658496) | `/test pull-cloud-provider-aws-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=cloud-provider-aws&pr=131). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Anicolehanjing). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/cloud-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
nicolehanjing: @andrewsykim updated! PTAL :)

Patch:
diff --git a/go.mod b/go.mod
index f4c691c..f65abf5 100644
--- a/go.mod
+++ b/go.mod
@@ -28,14 +28,20 @@ replace (
 
 require (
 	github.com/aws/aws-sdk-go v1.28.2
+	github.com/golang/mock v1.3.1
+	github.com/google/go-cmp v0.4.0
+	github.com/google/uuid v1.1.1
 	github.com/spf13/cobra v1.0.0
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.4.0
+	gopkg.in/gcfg.v1 v1.2.0
+	k8s.io/api v0.0.0
 	k8s.io/apimachinery v0.0.0
 	k8s.io/apiserver v0.0.0
 	k8s.io/cloud-provider v0.0.0
 	k8s.io/component-base v0.0.0
 	k8s.io/klog v1.0.0
+	k8s.io/klog/v2 v2.2.0
 	k8s.io/kubernetes v1.19.1
 	k8s.io/legacy-cloud-providers v0.0.0
 	k8s.io/utils v0.0.0-20200729134348-d5654de09c73
diff --git a/go.sum b/go.sum
index 1574a9f..1afa2e6 100644
--- a/go.sum
+++ b/go.sum
@@ -262,7 +262,9 @@ github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
 github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
@@ -887,6 +889,7 @@ k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL
 k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
 k8s.io/kubernetes v1.19.1 h1:3Gdl9EtBiV3SYuzml1915nFLVxlx08L6bRz1b07C60k=
 k8s.io/kubernetes v1.19.1/go.mod h1:yhT1/ltQajQsha3tnYc9QPFYSumGM45nlZdjf7WqE1A=
+k8s.io/kubernetes v1.19.2 h1:sEvBYVM1/H5hqejFR10u8ndreYARV3DiTrqi2AY31ok=
 k8s.io/kubernetes/staging/src/k8s.io/api v0.0.0-20200909111720-206bcadf021e h1:mdkfNp9Iu1erEZy/9kefsctTTyey3aAMcCQ5VTzScIE=
 k8s.io/kubernetes/staging/src/k8s.io/api v0.0.0-20200909111720-206bcadf021e/go.mod h1:Y4VjjNur38HL6/QxaTVK2yno1zjEQlvcvwbbRQs2DtQ=
 k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20200909111720-206bcadf021e/go.mod h1:BvtZU215FgO19Oy19K6h8qwajFfjxYqGewgjuYHWGRw=
diff --git a/pkg/providers/v2/cloud.go b/pkg/providers/v2/cloud.go
index 973190f..a07524b 100644
--- a/pkg/providers/v2/cloud.go
+++ b/pkg/providers/v2/cloud.go
@@ -28,6 +28,8 @@ import (
 	"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
 	"github.com/aws/aws-sdk-go/aws/ec2metadata"
 	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/ec2"
+
 	cloudprovider "k8s.io/cloud-provider"
 )
 
@@ -46,8 +48,11 @@ var _ cloudprovider.Interface = (*cloud)(nil)
 
 // cloud is the AWS v2 implementation of the cloud provider interface
 type cloud struct {
-	creds  *credentials.Credentials
-	region string
+	creds     *credentials.Credentials
+	instances cloudprovider.InstancesV2
+	region    string
+	ec2       EC2
+	metadata  EC2Metadata
 }
 
 // EC2Metadata is an abstraction over the AWS metadata service.
@@ -107,10 +112,33 @@ func newCloud() (cloudprovider.Interface, error) {
 		return nil, err
 	}
 
-	return &cloud{
-		creds:  creds,
-		region: region,
-	}, nil
+	instances, err := newInstances(az, creds)
+	if err != nil {
+		return nil, err
+	}
+
+	ec2Sess, err := session.NewSession(&aws.Config{
+		Region:      aws.String(region),
+		Credentials: creds,
+	})
+	if err != nil {
+		return nil, fmt.Errorf("unable to initialize AWS session: %v", err)
+	}
+
+	ec2Service := ec2.New(ec2Sess)
+	if err != nil {
+		return nil, fmt.Errorf("error creating AWS ec2 client: %q", err)
+	}
+
+	awsCloud := &cloud{
+		creds:     creds,
+		instances: instances,
+		region:    region,
+		metadata:  metadataClient,
+		ec2:       ec2Service,
+	}
+
+	return awsCloud, nil
 }
 
 // Initialize passes a Kubernetes clientBuilder interface to the cloud provider
@@ -158,5 +186,5 @@ func (c *cloud) HasClusterID() bool {
 // Also returns true if the interface is supported, false otherwise.
 // WARNING: InstancesV2 is an experimental interface and is subject to change in v1.20.
 func (c *cloud) InstancesV2() (cloudprovider.InstancesV2, bool) {
-	return nil, false
+	return c.instances, true
 }
diff --git a/pkg/providers/v2/instances.go b/pkg/providers/v2/instances.go
new file mode 100644
index 0000000..879fd1f
--- /dev/null
+++ b/pkg/providers/v2/instances.go
@@ -0,0 +1,281 @@
+/*
+Copyright 2020 The Kubernetes Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package v2 is an out-of-tree only implementation of the AWS cloud provider.
+// It is not compatible with v1 and should only be used on new clusters.
+package v2
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net"
+	"strings"
+
+	"k8s.io/klog/v2"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/credentials"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/ec2"
+
+	v1 "k8s.io/api/core/v1"
+	cloudprovider "k8s.io/cloud-provider"
+)
+
+// newInstances returns an implementation of cloudprovider.InstancesV2
+func newInstances(az string, creds *credentials.Credentials) (cloudprovider.InstancesV2, error) {
+	region, err := azToRegion(az)
+	if err != nil {
+		return nil, err
+	}
+
+	awsConfig := &aws.Config{
+		Region:      aws.String(region),
+		Credentials: creds,
+	}
+	awsConfig = awsConfig.WithCredentialsChainVerboseErrors(true)
+
+	sess, err := session.NewSession(awsConfig)
+	if err != nil {
+		return nil, fmt.Errorf("error creating new session: %v", err)
+	}
+	ec2Service := ec2.New(sess)
+
+	return &instances{
+		availabilityZone: az,
+		ec2:              ec2Service,
+		region:           region,
+	}, nil
+}
+
+// EC2 is an interface defining only the methods we call from the AWS EC2 SDK.
+type EC2 interface {
+	DescribeInstances(request *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
+}
+
+// instances is an implementation of cloudprovider.InstancesV2
+type instances struct {
+	availabilityZone string
+	ec2              EC2
+	region           string
+}
+
+// InstanceExists indicates whether a given node exists according to the cloud provider
+func (i *instances) InstanceExists(ctx context.Context, node *v1.Node) (bool, error) {
+	_, err := i.getInstance(ctx, node)
+
+	if err == cloudprovider.InstanceNotFound {
+		klog.V(6).Infof("instance not found for node: %s", node.Name)
+		return false, nil
+	}
+
+	if err != nil {
+		return false, err
+	}
+
+	return true, nil
+}
+
+// InstanceShutdown returns true if the instance is shutdown according to the cloud provider.
+func (i *instances) InstanceShutdown(ctx context.Context, node *v1.Node) (bool, error) {
+	ec2Instance, err := i.getInstance(ctx, node)
+	if err != nil {
+		return false, err
+	}
+
+	if ec2Instance.State != nil {
+		state := aws.StringValue(ec2Instance.State.Name)
+		// valid state for detaching volumes
+		if state == ec2.InstanceStateNameStopped {
+			return true, nil
+		}
+	}
+
+	return false, nil
+}
+
+// InstanceMetadata returns the instance's metadata.
+func (i *instances) InstanceMetadata(ctx context.Context, node *v1.Node) (*cloudprovider.InstanceMetadata, error) {
+	var err error
+	var ec2Instance *ec2.Instance
+
+	//  TODO: support node name policy other than private DNS names
+	ec2Instance, err = i.getInstance(ctx, node)
+	if err != nil {
+		return nil, err
+	}
+
+	nodeAddresses, err := nodeAddressesForInstance(ec2Instance)
+	if err != nil {
+		return nil, err
+	}
+
+	providerID, err := getInstanceProviderID(ec2Instance)
+	if err != nil {
+		return nil, err
+	}
+
+	metadata := &cloudprovider.InstanceMetadata{
+		ProviderID:    providerID,
+		InstanceType:  aws.StringValue(ec2Instance.InstanceType),
+		NodeAddresses: nodeAddresses,
+	}
+
+	return metadata, nil
+}
+
+// getInstance returns the instance if the instance with the given node info still exists.
+// If false an error will be returned, the instance will be immediately deleted by the cloud controller manager.
+func (i *instances) getInstance(ctx context.Context, node *v1.Node) (*ec2.Instance, error) {
+	var request *ec2.DescribeInstancesInput
+	if node.Spec.ProviderID == "" {
+		// get Instance by private DNS name
+		request = &ec2.DescribeInstancesInput{
+			Filters: []*ec2.Filter{
+				newEc2Filter("private-dns-name", node.Name),
+			},
+		}
+		klog.V(4).Infof("looking for node by private DNS name %v", node.Name)
+	} else {
+		// get Instance by provider ID
+		instanceID, err := parseInstanceIDFromProviderID(node.Spec.ProviderID)
+		if err != nil {
+			return nil, err
+		}
+
+		request = &ec2.DescribeInstancesInput{
+			InstanceIds: []*string{aws.String(instanceID)},
+		}
+		klog.V(4).Infof("looking for node by provider ID %v", node.Spec.ProviderID)
+	}
+
+	instances := []*ec2.Instance{}
+	var nextToken *string
+	for {
+		response, err := i.ec2.DescribeInstances(request)
+		if err != nil {
+			return nil, fmt.Errorf("error describing ec2 instances: %v", err)
+		}
+
+		for _, reservation := range response.Reservations {
+			instances = append(instances, reservation.Instances...)
+		}
+
+		nextToken = response.NextToken
+		if aws.StringValue(nextToken) == "" {
+			break
+		}
+		request.NextToken = nextToken
+	}
+
+	if len(instances) == 0 {
+		return nil, cloudprovider.InstanceNotFound
+	}
+
+	if len(instances) > 1 {
+		return nil, errors.New("getInstance: multiple instances found")
+	}
+
+	state := instances[0].State.Name
+	if *state == ec2.InstanceStateNameTerminated {
+		return nil, cloudprovider.InstanceNotFound
+	}
+
+	return instances[0], nil
+}
+
+// nodeAddresses for Instance returns a list of v1.NodeAddress for the give instance.
+// TODO: should we support ExternalIP by default?
+func nodeAddressesForInstance(instance *ec2.Instance) ([]v1.NodeAddress, error) {
+	if instance == nil {
+		return nil, errors.New("provided instances is nil")
+	}
+
+	addresses := []v1.NodeAddress{}
+	for _, networkInterface := range instance.NetworkInterfaces {
+		// skip network interfaces that are not currently in use
+		if aws.StringValue(networkInterface.Status) != ec2.NetworkInterfaceStatusInUse {
+			continue
+		}
+
+		for _, privateIP := range networkInterface.PrivateIpAddresses {
+			if ipAddress := aws.StringValue(privateIP.PrivateIpAddress); ipAddress != "" {
+				ip := net.ParseIP(ipAddress)
+				if ip == nil {
+					return nil, fmt.Errorf("invalid IP address %q from instance %q", ipAddress, aws.StringValue(instance.InstanceId))
+				}
+
+				addresses = append(addresses, v1.NodeAddress{
+					Type:    v1.NodeInternalIP,
+					Address: ip.String(),
+				})
+			}
+		}
+	}
+
+	return addresses, nil
+}
+
+// getInstanceProviderID returns the provider ID of an instance which is ultimately set in the node.Spec.ProviderID field.
+// The well-known format for a node's providerID is:
+//    * aws:///<availability-zone>/<instance-id>
+func getInstanceProviderID(instance *ec2.Instance) (string, error) {
+	if aws.StringValue(instance.Placement.AvailabilityZone) == "" {
+		return "", errors.New("instance availability zone was not set")
+	}
+
+	if aws.StringValue(instance.InstanceId) == "" {
+		return "", errors.New("instance ID was not set")
+	}
+
+	return "aws:///" + aws.StringValue(instance.Placement.AvailabilityZone) + "/" + aws.StringValue(instance.InstanceId), nil
+}
+
+// parseInstanceIDFromProviderID parses the node's instance ID based on the following formats:
+//   * aws://<availability-zone>/<instance-id>
+//   * aws:///<instance-id>
+//   * <instance-id>
+// This function always assumes a valid providerID format was provided.
+func parseInstanceIDFromProviderID(providerID string) (string, error) {
+	// trim the provider name prefix 'aws://', renaming providerID should contain metadata in one of the following formats:
+	// * <availability-zone>/<instance-id>
+	// * /<availability-zone>/<instance-id>
+	// * <instance-id>
+	instanceID := ""
+	metadata := strings.Split(strings.TrimPrefix(providerID, "aws://"), "/")
+	if len(metadata) == 1 {
+		// instance-id
+		instanceID = metadata[0]
+	} else if len(metadata) == 2 {
+		// az/instance-id
+		instanceID = metadata[1]
+	} else if len(metadata) == 3 {
+		// /az/instance-id
+		instanceID = metadata[2]
+	}
+
+	return instanceID, nil
+}
+
+func newEc2Filter(name string, values ...string) *ec2.Filter {
+	filter := &ec2.Filter{
+		Name: aws.String(name),
+	}
+
+	for _, value := range values {
+		filter.Values = append(filter.Values, aws.String(value))
+	}
+
+	return filter
+}
diff --git a/pkg/providers/v2/mocks/mock_ec2.go b/pkg/providers/v2/mocks/mock_ec2.go
new file mode 100644
index 0000000..9818a8f
--- /dev/null
+++ b/pkg/providers/v2/mocks/mock_ec2.go
@@ -0,0 +1,62 @@
+/*
+Copyright The Kubernetes Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by MockGen. DO NOT EDIT.
+// Source: instances.go
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+	ec2 "github.com/aws/aws-sdk-go/service/ec2"
+	gomock "github.com/golang/mock/gomock"
+	reflect "reflect"
+)
+
+// MockEC2 is a mock of EC2 interface
+type MockEC2 struct {
+	ctrl     *gomock.Controller
+	recorder *MockEC2MockRecorder
+}
+
+// MockEC2MockRecorder is the mock recorder for MockEC2
+type MockEC2MockRecorder struct {
+	mock *MockEC2
+}
+
+// NewMockEC2 creates a new mock instance
+func NewMockEC2(ctrl *gomock.Controller) *MockEC2 {
+	mock := &MockEC2{ctrl: ctrl}
+	mock.recorder = &MockEC2MockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockEC2) EXPECT() *MockEC2MockRecorder {
+	return m.recorder
+}
+
+// DescribeInstances mocks base method
+func (m *MockEC2) DescribeInstances(request *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DescribeInstances", request)
+	ret0, _ := ret[0].(*ec2.DescribeInstancesOutput)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// DescribeInstances indicates an expected call of DescribeInstances
+func (mr *MockEC2MockRecorder) DescribeInstances(request interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeInstances", reflect.TypeOf((*MockEC2)(nil).DescribeInstances), request)
+}


Test patch:
diff --git a/pkg/providers/v2/instances_test.go b/pkg/providers/v2/instances_test.go
new file mode 100644
index 0000000..e5360d6
--- /dev/null
+++ b/pkg/providers/v2/instances_test.go
@@ -0,0 +1,470 @@
+/*
+Copyright 2020 The Kubernetes Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package v2 is an out-of-tree only implementation of the AWS cloud provider.
+// It is not compatible with v1 and should only be used on new clusters.
+package v2
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ec2"
+	"github.com/golang/mock/gomock"
+	"github.com/google/go-cmp/cmp"
+	"github.com/stretchr/testify/assert"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	cloudprovider "k8s.io/cloud-provider"
+	"k8s.io/cloud-provider-aws/pkg/providers/v2/mocks"
+)
+
+const TestClusterID = "clusterid.test"
+
+func makeInstance(num int, privateIP, publicIP, privateDNSName, publicDNSName string, stateName string) *ec2.Instance {
+	instance := ec2.Instance{
+		InstanceId:       aws.String(fmt.Sprintf("i-%d", num)),
+		PrivateDnsName:   aws.String(privateDNSName),
+		PrivateIpAddress: aws.String(privateIP),
+		PublicDnsName:    aws.String(publicDNSName),
+		PublicIpAddress:  aws.String(publicIP),
+		InstanceType:     aws.String("c3.large"),
+		Placement:        &ec2.Placement{AvailabilityZone: aws.String("us-west-1a")},
+		State: &ec2.InstanceState{
+			Name: aws.String(stateName),
+		},
+		NetworkInterfaces: []*ec2.InstanceNetworkInterface{
+			{
+				Status: aws.String(ec2.NetworkInterfaceStatusInUse),
+				PrivateIpAddresses: []*ec2.InstancePrivateIpAddress{
+					{
+						PrivateIpAddress: aws.String(privateIP),
+					},
+				},
+			},
+		},
+	}
+
+	return &instance
+}
+
+func makeNode(nodeName string) *v1.Node {
+	providerID := "aws://us-west-1a/i-1234"
+	return &v1.Node{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: nodeName,
+		},
+		Spec: v1.NodeSpec{
+			ProviderID: providerID,
+		},
+	}
+}
+
+func makeNodeWithoutProviderID(nodeName string) *v1.Node {
+	return &v1.Node{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: nodeName,
+		},
+	}
+}
+
+func TestGetInstanceProviderID(t *testing.T) {
+	testCases := []struct {
+		name       string
+		instance   *ec2.Instance
+		providerID string
+	}{
+		{
+			name:       "get instance (regular) provider ID",
+			instance:   makeInstance(0, "192.168.0.1", "1.2.3.4", "instance-same.ec2.internal", "instance-same.ec2.external", "running"),
+			providerID: "aws:///us-west-1a/i-0",
+		},
+		{
+			name:       "get instance (without public IP/DNS) provider ID",
+			instance:   makeInstance(1, "192.168.0.2", "", "instance-same.ec2.internal", "", "running"),
+			providerID: "aws:///us-west-1a/i-1",
+		},
+		{
+			name:       "get instance (without private IP/DNS) provider ID",
+			instance:   makeInstance(2, "", "1.2.3.4", "", "instance-same.ec2.external", "running"),
+			providerID: "aws:///us-west-1a/i-2",
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			providerID, err := getInstanceProviderID(testCase.instance)
+			assert.NoError(t, err)
+			assert.Equal(t, testCase.providerID, providerID)
+		})
+	}
+}
+
+func TestInstanceExists(t *testing.T) {
+	mockCtrl := gomock.NewController(t)
+	defer mockCtrl.Finish()
+
+	mockEC2 := mocks.NewMockEC2(mockCtrl)
+
+	fakeInstances := &instances{
+		ec2: mockEC2,
+	}
+
+	nodeName := "ip-192-168-0-1.ec2.internal"
+
+	tests := []struct {
+		name            string
+		node            *v1.Node
+		mockedEC2Output *ec2.DescribeInstancesOutput
+		expectedResult  bool
+	}{
+		{
+			name: "test InstanceExists with running instance",
+			node: makeNode(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{
+							makeInstance(0, "192.168.0.1", "1.2.3.4", "instance-same.ec2.internal", "instance-same.ec2.external", "running"),
+						},
+					},
+				},
+			},
+			expectedResult: true,
+		},
+		{
+			name: "test InstanceExists with stopping instance",
+			node: makeNode(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{
+							makeInstance(1, "192.168.0.1", "1.2.3.4", "instance-same.ec2.internal", "instance-same.ec2.external", "stopping"),
+						},
+					},
+				},
+			},
+			expectedResult: true,
+		},
+		{
+			name: "test InstanceExists with terminated instance (node without providerID)",
+			node: makeNodeWithoutProviderID(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{},
+					},
+				},
+			},
+			expectedResult: false,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			mockEC2.EXPECT().DescribeInstances(gomock.Any()).Return(test.mockedEC2Output, nil)
+
+			exists, err := fakeInstances.InstanceExists(context.TODO(), test.node)
+
+			if err != nil {
+				t.Errorf("InstanceExists failed with node %v: %v", nodeName, err)
+			}
+
+			if exists != test.expectedResult {
+				t.Errorf("unexpected result, InstanceExists should return %v", test.expectedResult)
+			}
+		})
+	}
+}
+
+func TestInstanceShutdown(t *testing.T) {
+	mockCtrl := gomock.NewController(t)
+	defer mockCtrl.Finish()
+
+	mockEC2 := mocks.NewMockEC2(mockCtrl)
+
+	fakeInstances := &instances{
+		ec2: mockEC2,
+	}
+
+	nodeName := "ip-192-168-0-1.ec2.internal"
+
+	tests := []struct {
+		name            string
+		node            *v1.Node
+		mockedEC2Output *ec2.DescribeInstancesOutput
+		expectedResult  bool
+	}{
+		{
+			name: "test InstanceShutdown with running instance",
+			node: makeNode(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{
+							makeInstance(0, "192.168.0.1", "1.2.3.6", "instance-same.ec2.internal", "instance-same.ec2.external", "running"),
+						},
+					},
+				},
+			},
+			expectedResult: false,
+		},
+		{
+			name: "test InstanceShutdown with running instance (node without providerID)",
+			node: makeNodeWithoutProviderID(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{
+							makeInstance(0, "192.168.0.1", "1.2.3.6", "instance-same.ec2.internal", "instance-same.ec2.external", "running"),
+						},
+					},
+				},
+			},
+			expectedResult: false,
+		},
+		{
+			name: "test InstanceShutdown with stopping instance",
+			node: makeNode(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{
+							makeInstance(0, "192.168.0.1", "1.2.3.6", "instance-same.ec2.internal", "instance-same.ec2.external", "stopping"),
+						},
+					},
+				},
+			},
+			expectedResult: false,
+		},
+		{
+			name: "test InstanceShutdown with stopped instance (node without providerID)",
+			node: makeNodeWithoutProviderID(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{
+							makeInstance(0, "192.168.0.1", "1.2.3.6", "instance-same.ec2.internal", "instance-same.ec2.external", "stopped"),
+						},
+					},
+				},
+			},
+			expectedResult: true,
+		},
+		{
+			name: "test InstanceShutdown with terminated instance",
+			node: makeNode(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{
+							makeInstance(0, "192.168.0.1", "1.2.3.6", "instance-same.ec2.internal", "instance-same.ec2.external", "terminated"),
+						},
+					},
+				},
+			},
+			expectedResult: false,
+		},
+		{
+			name: "test InstanceShutdown with terminated instance (node without provierID)",
+			node: makeNodeWithoutProviderID(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{
+							makeInstance(0, "192.168.0.1", "1.2.3.6", "instance-same.ec2.internal", "instance-same.ec2.external", "terminated"),
+						},
+					},
+				},
+			},
+			expectedResult: false,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			mockEC2.EXPECT().DescribeInstances(gomock.Any()).Return(test.mockedEC2Output, nil)
+
+			shutdown, err := fakeInstances.InstanceShutdown(context.TODO(), test.node)
+
+			if err != nil {
+				t.Logf("InstanceShutdown failed with node %v: %v", nodeName, err)
+			}
+
+			if shutdown != test.expectedResult {
+				t.Errorf("unexpected result, InstanceShutdown should return %v", test.expectedResult)
+			}
+		})
+	}
+}
+
+func TestInstanceMetadata(t *testing.T) {
+	mockCtrl := gomock.NewController(t)
+	defer mockCtrl.Finish()
+
+	mockEC2 := mocks.NewMockEC2(mockCtrl)
+
+	fakeInstances := &instances{
+		ec2: mockEC2,
+	}
+
+	nodeName := "ip-192-168-0-1.ec2.internal"
+
+	tests := []struct {
+		name            string
+		node            *v1.Node
+		expectedResult  *cloudprovider.InstanceMetadata
+		mockedEC2Output *ec2.DescribeInstancesOutput
+	}{
+		{
+			name: "test InstanceMetadata with running instance",
+			node: makeNode(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{
+							makeInstance(0, "192.168.0.1", "1.2.3.6", "instance-same.ec2.internal", "instance-same.ec2.external", "running"),
+						},
+					},
+				},
+			},
+			expectedResult: &cloudprovider.InstanceMetadata{
+				ProviderID:   "aws:///us-west-1a/i-0",
+				InstanceType: "c3.large",
+				NodeAddresses: []v1.NodeAddress{
+					{
+						Type:    "InternalIP",
+						Address: "192.168.0.1",
+					},
+				},
+			},
+		},
+		{
+			name: "test InstanceMetadata with running instance (node without providerID)",
+			node: makeNodeWithoutProviderID(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{
+							makeInstance(0, "192.168.0.1", "1.2.3.6", "instance-same.ec2.internal", "instance-same.ec2.external", "running"),
+						},
+					},
+				},
+			},
+			expectedResult: &cloudprovider.InstanceMetadata{
+				ProviderID:   "aws:///us-west-1a/i-0",
+				InstanceType: "c3.large",
+				NodeAddresses: []v1.NodeAddress{
+					{
+						Type:    "InternalIP",
+						Address: "192.168.0.1",
+					},
+				},
+			},
+		},
+		{
+			name: "test InstanceMetadata with stopping instance",
+			node: makeNode(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{
+							makeInstance(1, "192.168.0.1", "1.2.3.6", "instance-same.ec2.internal", "instance-same.ec2.external", "stopping"),
+						},
+					},
+				},
+			},
+			expectedResult: &cloudprovider.InstanceMetadata{
+				ProviderID:   "aws:///us-west-1a/i-1",
+				InstanceType: "c3.large",
+				NodeAddresses: []v1.NodeAddress{
+					{
+						Type:    "InternalIP",
+						Address: "192.168.0.1",
+					},
+				},
+			},
+		},
+		{
+			name: "test InstanceMetadata with terminated instance",
+			node: makeNode(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{},
+					},
+				},
+			},
+			expectedResult: nil,
+		},
+		{
+			name: "test InstanceMetadata with terminated instance (node without providerID)",
+			node: makeNodeWithoutProviderID(nodeName),
+			mockedEC2Output: &ec2.DescribeInstancesOutput{
+				Reservations: []*ec2.Reservation{
+					{
+						Instances: []*ec2.Instance{},
+					},
+				},
+			},
+			expectedResult: nil,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			mockEC2.EXPECT().DescribeInstances(gomock.Any()).Return(test.mockedEC2Output, nil)
+
+			nodeName := "ip-172-21-32-3.ec2.internal"
+			node := &v1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: nodeName,
+				},
+			}
+			metadata, err := fakeInstances.InstanceMetadata(context.TODO(), node)
+
+			if err != nil {
+				t.Logf("InstanceMetadata failed with node %v: %v", nodeName, err)
+			}
+
+			if !cmp.Equal(metadata, test.expectedResult) {
+				t.Errorf("unexpected metadata %v, InstanceMetadata should return %v", metadata, test.expectedResult)
+			}
+		})
+	}
+}
+
+func TestParseInstanceIDFromProviderID(t *testing.T) {
+	testCases := []struct {
+		providerID string
+		instanceID string
+	}{
+		{"aws://eu-central-1a/i-1238asjd8asdm123", "i-1238asjd8asdm123"},
+		{"aws://us-west-2a/i-112as321asjd8asdm23", "i-112as321asjd8asdm23"},
+		{"aws://us-iso-east-1a/i-123", "i-123"},
+		{"aws://us-isob-east-1a/i-abcdef", "i-abcdef"},
+		{"aws:///us-isob-east-1a/i-abCDef", "i-abCDef"},
+		{"aws://us-east-1a/8asdm23", "8asdm23"},
+		{"aws:///us-west-2a/i-0226b64168e09815e", "i-0226b64168e09815e"},
+		{"i-0226b64168e09815e", "i-0226b64168e09815e"},
+	}
+
+	for _, testCase := range testCases {
+		ret, err := parseInstanceIDFromProviderID(testCase.providerID)
+		assert.NoError(t, err)
+		assert.Equal(t, testCase.instanceID, ret)
+	}
+}