Skip to content

Commit

Permalink
Add CAPI VMs to projects (#26)
Browse files Browse the repository at this point in the history
- Allows adding CAPI cluster VMs to Projects
   Note: Projects need to be created manually
  • Loading branch information
yannickstruyf3 authored Apr 25, 2022
1 parent 6561f5f commit d6f3e7f
Show file tree
Hide file tree
Showing 11 changed files with 298 additions and 4 deletions.
4 changes: 4 additions & 0 deletions api/v1alpha4/nutanixcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ type NutanixClusterSpec struct {
// host can be either DNS name or ip address
// +optional
ControlPlaneEndpoint capiv1.APIEndpoint `json:"controlPlaneEndpoint"`

// Add the Cluster resources to a Prism Central project
// +optional
Project *NutanixResourceIdentifier `json:"project"`
}

// NutanixClusterStatus defines the observed state of NutanixCluster
Expand Down
2 changes: 2 additions & 0 deletions api/v1alpha4/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion api/v1alpha4/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions api/v1beta1/nutanixcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ type NutanixClusterSpec struct {
// host can be either DNS name or ip address
// +optional
ControlPlaneEndpoint capiv1.APIEndpoint `json:"controlPlaneEndpoint"`

// Add the Cluster resources to a Prism Central project
// +optional
Project *NutanixResourceIdentifier `json:"project"`
}

// NutanixClusterStatus defines the observed state of NutanixCluster
Expand Down
7 changes: 6 additions & 1 deletion api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions cluster-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ spec:
controlPlaneEndpoint:
host: "${CONTROL_PLANE_ENDPOINT_IP}"
port: ${CONTROL_PLANE_ENDPOINT_PORT=6443}
# Adds the cluster virtual machines to a project defined in Prism Central.
# Replace NUTANIX_PROJECT_NAME with the correct project defined in Prism Central
# Note: Project must already be present in Prism Central.
# project:
# type: name
# name: "NUTANIX_PROJECT_NAME"

---
apiVersion: cluster.x-k8s.io/v1beta1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,24 @@ spec:
- host
- port
type: object
project:
description: Add the Cluster resources to a Prism Central project
properties:
name:
description: name is the resource name in the PC
type: string
type:
description: Type is the identifier type to use for this resource.
enum:
- uuid
- name
type: string
uuid:
description: uuid is the UUID of the resource in the PC.
type: string
required:
- type
type: object
type: object
status:
description: NutanixClusterStatus defines the observed state of NutanixCluster
Expand Down Expand Up @@ -191,6 +209,24 @@ spec:
- host
- port
type: object
project:
description: Add the Cluster resources to a Prism Central project
properties:
name:
description: name is the resource name in the PC
type: string
type:
description: Type is the identifier type to use for this resource.
enum:
- uuid
- name
type: string
uuid:
description: uuid is the UUID of the resource in the PC.
type: string
required:
- type
type: object
type: object
status:
description: NutanixClusterStatus defines the observed state of NutanixCluster
Expand Down
40 changes: 40 additions & 0 deletions controllers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,3 +508,43 @@ func getCategoryVMSpec(client *nutanixClientV3.Client, categoryIdentifiers []*in
}
return categorySpec, nil
}

func getProjectUUID(client *nutanixClientV3.Client, projectName, projectUUID *string) (string, error) {
var foundProjectUUID string
if projectUUID == nil && projectName == nil {
return "", fmt.Errorf("name or uuid must be passed in order to retrieve the project")
}
if projectUUID != nil {
projectIntentResponse, err := client.V3.GetProject(*projectUUID)
if err != nil {
if strings.Contains(fmt.Sprint(err), "ENTITY_NOT_FOUND") {
return "", fmt.Errorf("failed to find project with UUID %s: %v", *projectUUID, err)
}
}
foundProjectUUID = *projectIntentResponse.Metadata.UUID
} else if projectName != nil {
filter := fmt.Sprintf("name==%s", *projectName)
responseProjects, err := client.V3.ListAllProject(filter)
if err != nil {
return "", err
}
foundProjects := make([]*nutanixClientV3.Project, 0)
for _, s := range responseProjects.Entities {
projectSpec := s.Spec
if projectSpec.Name == *projectName {
foundProjects = append(foundProjects, s)
}
}
if len(foundProjects) == 0 {
return "", fmt.Errorf("failed to retrieve project by name %s", *projectName)
} else if len(foundProjects) > 1 {
return "", fmt.Errorf("more than one project found with name %s", *projectName)
} else {
foundProjectUUID = *foundProjects[0].Metadata.UUID
}
if foundProjectUUID == "" {
return "", fmt.Errorf("failed to retrieve project by name or uuid. Verify input parameters.")
}
}
return foundProjectUUID, nil
}
42 changes: 40 additions & 2 deletions controllers/nutanixmachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import (
const (
// provideridFmt is "nutanix://<vmUUID"
provideridFmt = "nutanix://%s"
projectKind = "project"
)

// NutanixMachineReconciler reconciles a NutanixMachine object
Expand Down Expand Up @@ -503,6 +504,15 @@ func (r *NutanixMachineReconciler) getOrCreateVM(rctx *nctx.MachineContext) (*nu
Categories: categories,
}

vmMetadataPtr := &vmMetadata
err = r.addVMToProject(rctx, vmMetadataPtr)
if err != nil {
errorMsg := fmt.Errorf("error occurred while trying to add VM %s to project: %v", vmName, err)
rctx.SetFailureStatus(capierrors.CreateMachineError, errorMsg)
klog.Errorf("%s %v", rctx.LogPrefix, errorMsg)
return nil, err
}

vmSpec.Resources = &nutanixClientV3.VMResources{
PowerState: utils.StringPtr("ON"),
HardwareClockTimezone: utils.StringPtr("UTC"),
Expand All @@ -522,11 +532,13 @@ func (r *NutanixMachineReconciler) getOrCreateVM(rctx *nctx.MachineContext) (*nu
vmSpecPtr := &vmSpec
err = r.addBootTypeToVM(rctx, vmSpecPtr)
if err != nil {
klog.Errorf("error occurred while adding boot type to vm spec: %v", err)
errorMsg := fmt.Errorf("error occurred while adding boot type to vm spec: %v", err)
rctx.SetFailureStatus(capierrors.CreateMachineError, errorMsg)
klog.Errorf("%s %v", rctx.LogPrefix, errorMsg)
return nil, err
}
vmInput.Spec = vmSpecPtr
vmInput.Metadata = &vmMetadata
vmInput.Metadata = vmMetadataPtr

vmResponse, err := client.V3.CreateVM(&vmInput)
if err != nil {
Expand Down Expand Up @@ -674,3 +686,29 @@ func (r *NutanixMachineReconciler) addBootTypeToVM(rctx *nctx.MachineContext, vm

return nil
}

func (r *NutanixMachineReconciler) addVMToProject(rctx *nctx.MachineContext, vmMetadata *nutanixClientV3.Metadata) error {

vmName := rctx.NutanixMachine.Name
projectRef := rctx.NutanixCluster.Spec.Project
if projectRef == nil {
klog.Infof("%s Not linking VM %s to a project", rctx.LogPrefix, vmName)
return nil
}
if vmMetadata == nil {
errorMsg := fmt.Errorf("%s metadata cannot be nil when adding VM %s to project", rctx.LogPrefix, vmName)
klog.Error(errorMsg)
return errorMsg
}
projectUUID, err := getProjectUUID(rctx.NutanixClient, projectRef.Name, projectRef.UUID)
if err != nil {
errorMsg := fmt.Errorf("%s error occurred while searching for project for VM %s: %v", rctx.LogPrefix, vmName, err)
klog.Error(errorMsg)
return errorMsg
}
vmMetadata.ProjectReference = &nutanixClientV3.Reference{
Kind: utils.StringPtr(projectKind),
UUID: utils.StringPtr(projectUUID),
}
return nil
}
87 changes: 87 additions & 0 deletions pkg/nutanix/v3/v3_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ type Service interface {
ListAllImage() (*ImageListIntentResponse, error)
ListAllCluster() (*ClusterListIntentResponse, error)
GetTask(taskUUID string) (*TasksResponse, error)
GetProject(projectUUID string) (*Project, error)
ListProject(getEntitiesRequest *DSMetadata) (*ProjectListResponse, error)
ListAllProject(filter string) (*ProjectListResponse, error)
}

/*CreateVM Creates a VM
Expand Down Expand Up @@ -1057,3 +1060,87 @@ func (op Operations) GetTask(taskUUID string) (*TasksResponse, error) {

return tasksTesponse, op.client.Do(ctx, req, tasksTesponse)
}

/*GetProject This operation gets a project.
*
* @param uuid The prject uuid - string.
* @return *Project
*/
func (op Operations) GetProject(projectUUID string) (*Project, error) {
ctx := context.TODO()

path := fmt.Sprintf("/projects/%s", projectUUID)
project := new(Project)

req, err := op.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, err
}

return project, op.client.Do(ctx, req, project)
}

/*ListProject gets a list of projects.
*
* @param metadata allows create filters to get specific data - *DSMetadata.
* @return *ProjectListResponse
*/
func (op Operations) ListProject(getEntitiesRequest *DSMetadata) (*ProjectListResponse, error) {
ctx := context.TODO()
path := "/projects/list"

projectList := new(ProjectListResponse)

req, err := op.client.NewRequest(ctx, http.MethodPost, path, getEntitiesRequest)
if err != nil {
return nil, err
}

return projectList, op.client.Do(ctx, req, projectList)
}

/*ListAllProject gets a list of projects
* This operation gets a list of Projects, allowing for sorting and pagination.
* Note: Entities that have not been created successfully are not listed.
* @return *ProjectListResponse
*/
func (op Operations) ListAllProject(filter string) (*ProjectListResponse, error) {
entities := make([]*Project, 0)

resp, err := op.ListProject(&DSMetadata{
Filter: &filter,
Kind: utils.StringPtr("project"),
Length: utils.Int64Ptr(itemsPerPage),
})
if err != nil {
return nil, err
}

totalEntities := utils.Int64Value(resp.Metadata.TotalMatches)
remaining := totalEntities
offset := utils.Int64Value(resp.Metadata.Offset)

if totalEntities > itemsPerPage {
for hasNext(&remaining) {
resp, err = op.ListProject(&DSMetadata{
Filter: &filter,
Kind: utils.StringPtr("project"),
Length: utils.Int64Ptr(itemsPerPage),
Offset: utils.Int64Ptr(offset),
})

if err != nil {
return nil, err
}

entities = append(entities, resp.Entities...)

offset += itemsPerPage
klog.V(5).Infof("[Debug] total=%d, remaining=%d, offset=%d len(entities)=%d\n", totalEntities, remaining, offset, len(entities))
}

resp.Entities = entities
}

return resp, nil
}
Loading

0 comments on commit d6f3e7f

Please sign in to comment.