From be6078257656e781c855d681d4c366ca9aa4cb28 Mon Sep 17 00:00:00 2001
From: Hans Rakers <h.rakers@global.leaseweb.com>
Date: Thu, 12 Sep 2024 15:13:51 +0200
Subject: [PATCH] feat: add support for CloudStack projects

---
 api/v1beta1/zz_generated.conversion.go        |   4 +-
 api/v1beta2/cloudstackfailuredomain_types.go  |  18 +-
 api/v1beta2/zz_generated.conversion.go        |  10 +-
 api/v1beta3/cloudstackfailuredomain_types.go  |  18 +-
 api/v1beta3/cloudstackmachine_types.go        |  10 -
 api/v1beta3/types.go                          |  11 +
 ...e.cluster.x-k8s.io_cloudstackclusters.yaml |  14 +-
 ...ter.x-k8s.io_cloudstackfailuredomains.yaml |  14 +-
 ...cloudstackaffinitygroup_controller_test.go |  41 +++
 controllers/utils/failuredomains.go           |   4 +-
 pkg/cloud/affinity_groups.go                  |   9 +-
 pkg/cloud/affinity_groups_test.go             |  18 +-
 pkg/cloud/client.go                           |  79 +++++-
 pkg/cloud/instance.go                         |  72 +++--
 pkg/cloud/instance_test.go                    | 254 ++++++++++++++----
 pkg/cloud/isolated_network.go                 |  15 +-
 pkg/cloud/isolated_network_test.go            |  18 +-
 pkg/cloud/network.go                          |   5 +-
 pkg/cloud/network_test.go                     |  10 +-
 pkg/cloud/tags.go                             |   1 +
 pkg/cloud/user_credentials.go                 |  45 ++++
 pkg/cloud/zone.go                             |   5 +-
 pkg/cloud/zone_test.go                        |  20 +-
 test/e2e/common.go                            |  20 +-
 test/e2e/config/cloudstack.yaml               |   2 +
 ...ster.x-k8s.io_cloudstackmachineowners.yaml |   2 +-
 26 files changed, 546 insertions(+), 173 deletions(-)

diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go
index c3ec54ba..5bd48651 100644
--- a/api/v1beta1/zz_generated.conversion.go
+++ b/api/v1beta1/zz_generated.conversion.go
@@ -944,8 +944,8 @@ func Convert_v1beta1_CloudStackZoneSpec_To_v1beta3_CloudStackZoneSpec(in *CloudS
 }
 
 func autoConvert_v1beta3_CloudStackZoneSpec_To_v1beta1_CloudStackZoneSpec(in *v1beta3.CloudStackZoneSpec, out *CloudStackZoneSpec, s conversion.Scope) error {
-	out.Name = in.Name
 	out.ID = in.ID
+	out.Name = in.Name
 	if err := Convert_v1beta3_Network_To_v1beta1_Network(&in.Network, &out.Network, s); err != nil {
 		return err
 	}
@@ -971,8 +971,8 @@ func Convert_v1beta1_Network_To_v1beta3_Network(in *Network, out *v1beta3.Networ
 
 func autoConvert_v1beta3_Network_To_v1beta1_Network(in *v1beta3.Network, out *Network, s conversion.Scope) error {
 	out.ID = in.ID
-	out.Type = in.Type
 	out.Name = in.Name
+	out.Type = in.Type
 	// WARNING: in.CIDR requires manual conversion: does not exist in peer-type
 	// WARNING: in.Domain requires manual conversion: does not exist in peer-type
 	return nil
diff --git a/api/v1beta2/cloudstackfailuredomain_types.go b/api/v1beta2/cloudstackfailuredomain_types.go
index 5af6fa88..4b5ce7d7 100644
--- a/api/v1beta2/cloudstackfailuredomain_types.go
+++ b/api/v1beta2/cloudstackfailuredomain_types.go
@@ -47,23 +47,23 @@ type Network struct {
 	// +optional
 	ID string `json:"id,omitempty"`
 
+	// Cloudstack Network Name the cluster is built in.
+	Name string `json:"name"`
+
 	// Cloudstack Network Type the cluster is built in.
 	// +optional
 	Type string `json:"type,omitempty"`
-
-	// Cloudstack Network Name the cluster is built in.
-	Name string `json:"name"`
 }
 
 // CloudStackZoneSpec specifies a Zone's details.
 type CloudStackZoneSpec struct {
-	// Name.
+	// Zone ID.
 	//+optional
-	Name string `json:"name,omitempty"`
+	ID string `json:"id,omitempty"`
 
-	// ID.
+	// Zone Name.
 	//+optional
-	ID string `json:"id,omitempty"`
+	Name string `json:"name,omitempty"`
 
 	// The network within the Zone to use.
 	Network Network `json:"network"`
@@ -85,6 +85,10 @@ type CloudStackFailureDomainSpec struct {
 	// +optional
 	Domain string `json:"domain,omitempty"`
 
+	// CloudStack project.
+	// +optional
+	Project string `json:"project,omitempty"`
+
 	// Apache CloudStack Endpoint secret reference.
 	ACSEndpoint corev1.SecretReference `json:"acsEndpoint"`
 }
diff --git a/api/v1beta2/zz_generated.conversion.go b/api/v1beta2/zz_generated.conversion.go
index ab439e1b..9382799d 100644
--- a/api/v1beta2/zz_generated.conversion.go
+++ b/api/v1beta2/zz_generated.conversion.go
@@ -681,6 +681,7 @@ func autoConvert_v1beta2_CloudStackFailureDomainSpec_To_v1beta3_CloudStackFailur
 	}
 	out.Account = in.Account
 	out.Domain = in.Domain
+	out.Project = in.Project
 	out.ACSEndpoint = in.ACSEndpoint
 	return nil
 }
@@ -697,6 +698,7 @@ func autoConvert_v1beta3_CloudStackFailureDomainSpec_To_v1beta2_CloudStackFailur
 	}
 	out.Account = in.Account
 	out.Domain = in.Domain
+	out.Project = in.Project
 	out.ACSEndpoint = in.ACSEndpoint
 	return nil
 }
@@ -1247,8 +1249,8 @@ func Convert_v1beta3_CloudStackResourceIdentifier_To_v1beta2_CloudStackResourceI
 }
 
 func autoConvert_v1beta2_CloudStackZoneSpec_To_v1beta3_CloudStackZoneSpec(in *CloudStackZoneSpec, out *v1beta3.CloudStackZoneSpec, s conversion.Scope) error {
-	out.Name = in.Name
 	out.ID = in.ID
+	out.Name = in.Name
 	if err := Convert_v1beta2_Network_To_v1beta3_Network(&in.Network, &out.Network, s); err != nil {
 		return err
 	}
@@ -1261,8 +1263,8 @@ func Convert_v1beta2_CloudStackZoneSpec_To_v1beta3_CloudStackZoneSpec(in *CloudS
 }
 
 func autoConvert_v1beta3_CloudStackZoneSpec_To_v1beta2_CloudStackZoneSpec(in *v1beta3.CloudStackZoneSpec, out *CloudStackZoneSpec, s conversion.Scope) error {
-	out.Name = in.Name
 	out.ID = in.ID
+	out.Name = in.Name
 	if err := Convert_v1beta3_Network_To_v1beta2_Network(&in.Network, &out.Network, s); err != nil {
 		return err
 	}
@@ -1276,8 +1278,8 @@ func Convert_v1beta3_CloudStackZoneSpec_To_v1beta2_CloudStackZoneSpec(in *v1beta
 
 func autoConvert_v1beta2_Network_To_v1beta3_Network(in *Network, out *v1beta3.Network, s conversion.Scope) error {
 	out.ID = in.ID
-	out.Type = in.Type
 	out.Name = in.Name
+	out.Type = in.Type
 	return nil
 }
 
@@ -1288,8 +1290,8 @@ func Convert_v1beta2_Network_To_v1beta3_Network(in *Network, out *v1beta3.Networ
 
 func autoConvert_v1beta3_Network_To_v1beta2_Network(in *v1beta3.Network, out *Network, s conversion.Scope) error {
 	out.ID = in.ID
-	out.Type = in.Type
 	out.Name = in.Name
+	out.Type = in.Type
 	// WARNING: in.CIDR requires manual conversion: does not exist in peer-type
 	// WARNING: in.Domain requires manual conversion: does not exist in peer-type
 	return nil
diff --git a/api/v1beta3/cloudstackfailuredomain_types.go b/api/v1beta3/cloudstackfailuredomain_types.go
index a757dd4f..6dd034be 100644
--- a/api/v1beta3/cloudstackfailuredomain_types.go
+++ b/api/v1beta3/cloudstackfailuredomain_types.go
@@ -47,13 +47,13 @@ type Network struct {
 	//+optional
 	ID string `json:"id,omitempty"`
 
+	// Cloudstack Network Name the cluster is built in.
+	Name string `json:"name"`
+
 	// Cloudstack Network Type the cluster is built in.
 	//+optional
 	Type string `json:"type,omitempty"`
 
-	// Cloudstack Network Name the cluster is built in.
-	Name string `json:"name"`
-
 	// CIDR is the IP address range of the network.
 	//+optional
 	CIDR string `json:"cidr,omitempty"`
@@ -65,13 +65,13 @@ type Network struct {
 
 // CloudStackZoneSpec specifies a Zone's details.
 type CloudStackZoneSpec struct {
-	// Name.
+	// Zone ID.
 	//+optional
-	Name string `json:"name,omitempty"`
+	ID string `json:"id,omitempty"`
 
-	// ID.
+	// Zone Name.
 	//+optional
-	ID string `json:"id,omitempty"`
+	Name string `json:"name,omitempty"`
 
 	// The network within the Zone to use.
 	Network Network `json:"network"`
@@ -93,6 +93,10 @@ type CloudStackFailureDomainSpec struct {
 	//+optional
 	Domain string `json:"domain,omitempty"`
 
+	// CloudStack project.
+	//+optional
+	Project string `json:"project,omitempty"`
+
 	// Apache CloudStack Endpoint secret reference.
 	ACSEndpoint corev1.SecretReference `json:"acsEndpoint"`
 }
diff --git a/api/v1beta3/cloudstackmachine_types.go b/api/v1beta3/cloudstackmachine_types.go
index 86b6901d..4c9c1cec 100644
--- a/api/v1beta3/cloudstackmachine_types.go
+++ b/api/v1beta3/cloudstackmachine_types.go
@@ -97,16 +97,6 @@ func (r *CloudStackMachine) CompressUserdata() bool {
 	return r.Spec.UncompressedUserData == nil || !*r.Spec.UncompressedUserData
 }
 
-type CloudStackResourceIdentifier struct {
-	// Cloudstack resource ID.
-	//+optional
-	ID string `json:"id,omitempty"`
-
-	// Cloudstack resource Name.
-	//+optional
-	Name string `json:"name,omitempty"`
-}
-
 type CloudStackResourceDiskOffering struct {
 	CloudStackResourceIdentifier `json:",inline"`
 	// Desired disk size. Used if disk offering is customizable as indicated by the ACS field 'Custom Disk Size'.
diff --git a/api/v1beta3/types.go b/api/v1beta3/types.go
index d2f73a35..33527b44 100644
--- a/api/v1beta3/types.go
+++ b/api/v1beta3/types.go
@@ -16,6 +16,17 @@ limitations under the License.
 
 package v1beta3
 
+// CloudStackResourceIdentifier is used to identify any CloudStack resource that can be referenced by either ID or Name.
+type CloudStackResourceIdentifier struct {
+	// Cloudstack resource ID.
+	//+optional
+	ID string `json:"id,omitempty"`
+
+	// Cloudstack resource Name.
+	//+optional
+	Name string `json:"name,omitempty"`
+}
+
 // LoadBalancer represents basic information about the associated OpenStack LoadBalancer.
 type LoadBalancer struct {
 	IPAddress   string `json:"ipAddress"`
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackclusters.yaml
index 68605579..fbd6cf9e 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackclusters.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackclusters.yaml
@@ -253,14 +253,17 @@ spec:
                     name:
                       description: The failure domain unique name.
                       type: string
+                    project:
+                      description: CloudStack project.
+                      type: string
                     zone:
                       description: The ACS Zone for this failure domain.
                       properties:
                         id:
-                          description: ID.
+                          description: Zone ID.
                           type: string
                         name:
-                          description: Name.
+                          description: Zone Name.
                           type: string
                         network:
                           description: The network within the Zone to use.
@@ -427,14 +430,17 @@ spec:
                     name:
                       description: The failure domain unique name.
                       type: string
+                    project:
+                      description: CloudStack project.
+                      type: string
                     zone:
                       description: The ACS Zone for this failure domain.
                       properties:
                         id:
-                          description: ID.
+                          description: Zone ID.
                           type: string
                         name:
-                          description: Name.
+                          description: Zone Name.
                           type: string
                         network:
                           description: The network within the Zone to use.
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackfailuredomains.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackfailuredomains.yaml
index 5c2ddde3..f26bba9a 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackfailuredomains.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackfailuredomains.yaml
@@ -63,14 +63,17 @@ spec:
               name:
                 description: The failure domain unique name.
                 type: string
+              project:
+                description: CloudStack project.
+                type: string
               zone:
                 description: The ACS Zone for this failure domain.
                 properties:
                   id:
-                    description: ID.
+                    description: Zone ID.
                     type: string
                   name:
-                    description: Name.
+                    description: Zone Name.
                     type: string
                   network:
                     description: The network within the Zone to use.
@@ -163,14 +166,17 @@ spec:
               name:
                 description: The failure domain unique name.
                 type: string
+              project:
+                description: CloudStack project.
+                type: string
               zone:
                 description: The ACS Zone for this failure domain.
                 properties:
                   id:
-                    description: ID.
+                    description: Zone ID.
                     type: string
                   name:
-                    description: Name.
+                    description: Zone Name.
                     type: string
                   network:
                     description: The network within the Zone to use.
diff --git a/controllers/cloudstackaffinitygroup_controller_test.go b/controllers/cloudstackaffinitygroup_controller_test.go
index ebeecdfc..39dca7a0 100644
--- a/controllers/cloudstackaffinitygroup_controller_test.go
+++ b/controllers/cloudstackaffinitygroup_controller_test.go
@@ -20,10 +20,12 @@ import (
 	"github.com/golang/mock/gomock"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
+	"k8s.io/apimachinery/pkg/api/errors"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
 
 	infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3"
+	"sigs.k8s.io/cluster-api-provider-cloudstack/pkg/cloud"
 	dummies "sigs.k8s.io/cluster-api-provider-cloudstack/test/dummies/v1beta3"
 )
 
@@ -38,11 +40,34 @@ var _ = Describe("CloudStackAffinityGroupReconciler", func() {
 		// Modify failure domain name the same way the cluster controller would.
 		dummies.CSAffinityGroup.Spec.FailureDomainName = dummies.CSFailureDomain1.Spec.Name
 
+		mockCloudClient.EXPECT().GetOrCreateAffinityGroup(gomock.Any()).AnyTimes()
+
 		Ω(k8sClient.Create(ctx, dummies.CSFailureDomain1)).Should(Succeed())
 		Ω(k8sClient.Create(ctx, dummies.CSAffinityGroup)).Should(Succeed())
 
+		// Test that the AffinityGroup controller sets Status.Ready to true.
+		Eventually(func() bool {
+			nameSpaceFilter := &client.ListOptions{Namespace: dummies.ClusterNameSpace}
+			affinityGroups := &infrav1.CloudStackAffinityGroupList{}
+			if err := k8sClient.List(ctx, affinityGroups, nameSpaceFilter); err == nil {
+				if len(affinityGroups.Items) == 1 {
+					return affinityGroups.Items[0].Status.Ready
+				}
+			}
+
+			return false
+		}, timeout).WithPolling(pollInterval).Should(BeTrue())
+	})
+
+	It("Should remove affinity group finalizer if corresponding affinity group is not present on Cloudstack.", func() {
+		// Modify failure domain name the same way the cluster controller would.
+		dummies.CSAffinityGroup.Spec.FailureDomainName = dummies.CSFailureDomain1.Spec.Name
+
 		mockCloudClient.EXPECT().GetOrCreateAffinityGroup(gomock.Any()).AnyTimes()
 
+		Ω(k8sClient.Create(ctx, dummies.CSFailureDomain1)).Should(Succeed())
+		Ω(k8sClient.Create(ctx, dummies.CSAffinityGroup)).Should(Succeed())
+
 		// Test that the AffinityGroup controller sets Status.Ready to true.
 		Eventually(func() bool {
 			nameSpaceFilter := &client.ListOptions{Namespace: dummies.ClusterNameSpace}
@@ -52,7 +77,23 @@ var _ = Describe("CloudStackAffinityGroupReconciler", func() {
 					return affinityGroups.Items[0].Status.Ready
 				}
 			}
+			return false
+		}, timeout).WithPolling(pollInterval).Should(BeTrue())
+
+		mockCloudClient.EXPECT().FetchAffinityGroup(gomock.Any()).Do(func(arg1 interface{}) {
+			arg1.(*cloud.AffinityGroup).ID = ""
+		}).AnyTimes().Return(nil)
+		Ω(k8sClient.Delete(ctx, dummies.CSAffinityGroup)).Should(Succeed())
 
+		Ω(k8sClient.Delete(ctx, dummies.CSAffinityGroup)).Should(Succeed())
+
+		// Once the affinity group id was set to "" the controller should remove the finalizer and unblock deleting affinity group resource
+		Eventually(func() bool {
+			retrievedAffinityGroup := &infrav1.CloudStackAffinityGroup{}
+			affinityGroupKey := client.ObjectKey{Namespace: dummies.ClusterNameSpace, Name: dummies.AffinityGroup.Name}
+			if err := k8sClient.Get(ctx, affinityGroupKey, retrievedAffinityGroup); err != nil {
+				return errors.IsNotFound(err)
+			}
 			return false
 		}, timeout).WithPolling(pollInterval).Should(BeTrue())
 	})
diff --git a/controllers/utils/failuredomains.go b/controllers/utils/failuredomains.go
index 22d535f7..7245d1e8 100644
--- a/controllers/utils/failuredomains.go
+++ b/controllers/utils/failuredomains.go
@@ -154,12 +154,12 @@ func (c *CloudClientImplementation) AsFailureDomainUser(fdSpec *infrav1.CloudSta
 		_ = c.K8sClient.Get(c.RequestCtx, key, clientConfig)
 
 		var err error
-		if c.CSClient, err = cloud.NewClientFromK8sSecret(endpointCredentials, clientConfig); err != nil {
+		if c.CSClient, err = cloud.NewClientFromK8sSecret(endpointCredentials, clientConfig, cloud.WithProject(fdSpec.Project)); err != nil {
 			return ctrl.Result{}, errors.Wrapf(err, "parsing ACSEndpoint secret with ref: %v", fdSpec.ACSEndpoint)
 		}
 
 		if fdSpec.Account != "" { // Set r.CSUser CloudStack Client per Account and Domain.
-			client, err := c.CSClient.NewClientInDomainAndAccount(fdSpec.Domain, fdSpec.Account)
+			client, err := c.CSClient.NewClientInDomainAndAccount(fdSpec.Domain, fdSpec.Account, cloud.WithProject(fdSpec.Project))
 			if err != nil {
 				return ctrl.Result{}, err
 			}
diff --git a/pkg/cloud/affinity_groups.go b/pkg/cloud/affinity_groups.go
index 60281f3c..105a7f9c 100644
--- a/pkg/cloud/affinity_groups.go
+++ b/pkg/cloud/affinity_groups.go
@@ -17,6 +17,7 @@ limitations under the License.
 package cloud
 
 import (
+	"github.com/apache/cloudstack-go/v2/cloudstack"
 	"github.com/pkg/errors"
 
 	infrav1 "sigs.k8s.io/cluster-api-provider-cloudstack/api/v1beta3"
@@ -45,7 +46,7 @@ type AffinityGroupIface interface {
 
 func (c *client) FetchAffinityGroup(group *AffinityGroup) error {
 	if group.ID != "" {
-		affinityGroup, count, err := c.cs.AffinityGroup.GetAffinityGroupByID(group.ID)
+		affinityGroup, count, err := c.cs.AffinityGroup.GetAffinityGroupByID(group.ID, cloudstack.WithProject(c.user.Project.ID))
 		if err != nil {
 			// handle via multierr
 			c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
@@ -61,7 +62,7 @@ func (c *client) FetchAffinityGroup(group *AffinityGroup) error {
 		return nil
 	}
 	if group.Name != "" {
-		affinityGroup, count, err := c.cs.AffinityGroup.GetAffinityGroupByName(group.Name)
+		affinityGroup, count, err := c.cs.AffinityGroup.GetAffinityGroupByName(group.Name, cloudstack.WithProject(c.user.Project.ID))
 		if err != nil {
 			// handle via multierr
 			c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
@@ -84,6 +85,7 @@ func (c *client) GetOrCreateAffinityGroup(group *AffinityGroup) error {
 	if err := c.FetchAffinityGroup(group); err != nil { // Group not found?
 		p := c.cs.AffinityGroup.NewCreateAffinityGroupParams(group.Name, group.Type)
 		p.SetName(group.Name)
+		setIfNotEmpty(c.user.Project.ID, p.SetProjectid)
 		resp, err := c.cs.AffinityGroup.CreateAffinityGroup(p)
 		if err != nil {
 			c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
@@ -100,6 +102,7 @@ func (c *client) DeleteAffinityGroup(group *AffinityGroup) error {
 	p := c.cs.AffinityGroup.NewDeleteAffinityGroupParams()
 	setIfNotEmpty(group.ID, p.SetId)
 	setIfNotEmpty(group.Name, p.SetName)
+	setIfNotEmpty(c.user.Project.ID, p.SetProjectid)
 	_, err := c.cs.AffinityGroup.DeleteAffinityGroup(p)
 	c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 
@@ -110,7 +113,7 @@ type affinityGroups []AffinityGroup
 
 func (c *client) getCurrentAffinityGroups(csMachine *infrav1.CloudStackMachine) (affinityGroups, error) {
 	// Start by fetching VM details which includes an array of currently associated affinity groups.
-	virtM, count, err := c.cs.VirtualMachine.GetVirtualMachineByID(*csMachine.Spec.InstanceID)
+	virtM, count, err := c.cs.VirtualMachine.GetVirtualMachineByID(*csMachine.Spec.InstanceID, cloudstack.WithProject(c.user.Project.ID))
 	if err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 
diff --git a/pkg/cloud/affinity_groups_test.go b/pkg/cloud/affinity_groups_test.go
index 3310f601..706b57f6 100644
--- a/pkg/cloud/affinity_groups_test.go
+++ b/pkg/cloud/affinity_groups_test.go
@@ -59,13 +59,13 @@ var _ = Describe("AffinityGroup Unit Tests", func() {
 	Context("Fetch or Create Affinity group", func() {
 		It("fetches an affinity group by Name", func() {
 			dummies.AffinityGroup.ID = "" // Force name fetching.
-			ags.EXPECT().GetAffinityGroupByName(dummies.AffinityGroup.Name).Return(&cloudstack.AffinityGroup{}, 1, nil)
+			ags.EXPECT().GetAffinityGroupByName(dummies.AffinityGroup.Name, gomock.Any()).Return(&cloudstack.AffinityGroup{}, 1, nil)
 
 			Ω(client.GetOrCreateAffinityGroup(dummies.AffinityGroup)).Should(Succeed())
 		})
 
 		It("fetches an affinity group by ID", func() {
-			ags.EXPECT().GetAffinityGroupByID(dummies.AffinityGroup.ID).Return(&cloudstack.AffinityGroup{}, 1, nil)
+			ags.EXPECT().GetAffinityGroupByID(dummies.AffinityGroup.ID, gomock.Any()).Return(&cloudstack.AffinityGroup{}, 1, nil)
 
 			Ω(client.GetOrCreateAffinityGroup(dummies.AffinityGroup)).Should(Succeed())
 		})
@@ -73,7 +73,7 @@ var _ = Describe("AffinityGroup Unit Tests", func() {
 		It("creates an affinity group", func() {
 			// dummies.SetDummyDomainAndAccount()
 			// dummies.SetDummyDomainID()
-			ags.EXPECT().GetAffinityGroupByID(dummies.AffinityGroup.ID).Return(nil, -1, fakeError)
+			ags.EXPECT().GetAffinityGroupByID(dummies.AffinityGroup.ID, gomock.Any()).Return(nil, -1, fakeError)
 			ags.EXPECT().NewCreateAffinityGroupParams(dummies.AffinityGroup.Name, dummies.AffinityGroup.Type).
 				Return(&cloudstack.CreateAffinityGroupParams{})
 			ags.EXPECT().CreateAffinityGroup(ParamMatch(And(NameEquals(dummies.AffinityGroup.Name)))).
@@ -85,7 +85,7 @@ var _ = Describe("AffinityGroup Unit Tests", func() {
 		It("creates an affinity group if Name provided returns more than one affinity group", func() {
 			dummies.AffinityGroup.ID = "" // Force name fetching.
 			agp := &cloudstack.CreateAffinityGroupParams{}
-			ags.EXPECT().GetAffinityGroupByName(dummies.AffinityGroup.Name).Return(&cloudstack.AffinityGroup{}, 2, nil)
+			ags.EXPECT().GetAffinityGroupByName(dummies.AffinityGroup.Name, gomock.Any()).Return(&cloudstack.AffinityGroup{}, 2, nil)
 			ags.EXPECT().NewCreateAffinityGroupParams(gomock.Any(), gomock.Any()).Return(agp)
 			ags.EXPECT().CreateAffinityGroup(agp).Return(&cloudstack.CreateAffinityGroupResponse{}, nil)
 
@@ -95,7 +95,7 @@ var _ = Describe("AffinityGroup Unit Tests", func() {
 		It("creates an affinity group if getting affinity group by name fails", func() {
 			dummies.AffinityGroup.ID = "" // Force name fetching.
 			agp := &cloudstack.CreateAffinityGroupParams{}
-			ags.EXPECT().GetAffinityGroupByName(dummies.AffinityGroup.Name).Return(nil, -1, fakeError)
+			ags.EXPECT().GetAffinityGroupByName(dummies.AffinityGroup.Name, gomock.Any()).Return(nil, -1, fakeError)
 			ags.EXPECT().NewCreateAffinityGroupParams(gomock.Any(), gomock.Any()).Return(agp)
 			ags.EXPECT().CreateAffinityGroup(agp).Return(&cloudstack.CreateAffinityGroupResponse{}, nil)
 
@@ -104,7 +104,7 @@ var _ = Describe("AffinityGroup Unit Tests", func() {
 
 		It("creates an affinity group if ID provided returns more than one affinity group", func() {
 			agp := &cloudstack.CreateAffinityGroupParams{}
-			ags.EXPECT().GetAffinityGroupByID(dummies.AffinityGroup.ID).Return(&cloudstack.AffinityGroup{}, 2, nil)
+			ags.EXPECT().GetAffinityGroupByID(dummies.AffinityGroup.ID, gomock.Any()).Return(&cloudstack.AffinityGroup{}, 2, nil)
 			ags.EXPECT().NewCreateAffinityGroupParams(gomock.Any(), gomock.Any()).Return(agp)
 			ags.EXPECT().CreateAffinityGroup(agp).Return(&cloudstack.CreateAffinityGroupResponse{}, nil)
 
@@ -113,7 +113,7 @@ var _ = Describe("AffinityGroup Unit Tests", func() {
 
 		It("creates an affinity group if getting affinity group by ID fails", func() {
 			agp := &cloudstack.CreateAffinityGroupParams{}
-			ags.EXPECT().GetAffinityGroupByID(dummies.AffinityGroup.ID).Return(nil, -1, fakeError)
+			ags.EXPECT().GetAffinityGroupByID(dummies.AffinityGroup.ID, gomock.Any()).Return(nil, -1, fakeError)
 			ags.EXPECT().NewCreateAffinityGroupParams(gomock.Any(), gomock.Any()).Return(agp)
 			ags.EXPECT().CreateAffinityGroup(agp).Return(&cloudstack.CreateAffinityGroupResponse{}, nil)
 
@@ -164,7 +164,7 @@ var _ = Describe("AffinityGroup Unit Tests", func() {
 	It("Associate affinity group", func() {
 		uagp := &cloudstack.UpdateVMAffinityGroupParams{}
 		vmp := &cloudstack.StartVirtualMachineParams{}
-		vms.EXPECT().GetVirtualMachineByID(*dummies.CSMachine1.Spec.InstanceID).Return(&cloudstack.VirtualMachine{}, 1, nil)
+		vms.EXPECT().GetVirtualMachineByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).Return(&cloudstack.VirtualMachine{}, 1, nil)
 		ags.EXPECT().NewUpdateVMAffinityGroupParams(*dummies.CSMachine1.Spec.InstanceID).Return(uagp)
 		vms.EXPECT().NewStopVirtualMachineParams(*dummies.CSMachine1.Spec.InstanceID).Return(&cloudstack.StopVirtualMachineParams{})
 		vms.EXPECT().StopVirtualMachine(&cloudstack.StopVirtualMachineParams{}).Return(&cloudstack.StopVirtualMachineResponse{State: "Stopping"}, nil)
@@ -177,7 +177,7 @@ var _ = Describe("AffinityGroup Unit Tests", func() {
 	It("Disassociate affinity group", func() {
 		uagp := &cloudstack.UpdateVMAffinityGroupParams{}
 		vmp := &cloudstack.StartVirtualMachineParams{}
-		vms.EXPECT().GetVirtualMachineByID(*dummies.CSMachine1.Spec.InstanceID).Return(&cloudstack.VirtualMachine{}, 1, nil)
+		vms.EXPECT().GetVirtualMachineByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).Return(&cloudstack.VirtualMachine{}, 1, nil)
 		ags.EXPECT().NewUpdateVMAffinityGroupParams(*dummies.CSMachine1.Spec.InstanceID).Return(uagp)
 		vms.EXPECT().NewStopVirtualMachineParams(*dummies.CSMachine1.Spec.InstanceID).Return(&cloudstack.StopVirtualMachineParams{})
 		vms.EXPECT().StopVirtualMachine(&cloudstack.StopVirtualMachineParams{}).Return(&cloudstack.StopVirtualMachineResponse{State: "Stopping"}, nil)
diff --git a/pkg/cloud/client.go b/pkg/cloud/client.go
index d57c1134..debfa80c 100644
--- a/pkg/cloud/client.go
+++ b/pkg/cloud/client.go
@@ -43,15 +43,16 @@ type Client interface {
 	ZoneIFace
 	IsoNetworkIface
 	UserCredIFace
-	NewClientInDomainAndAccount(domain string, account string) (Client, error)
+	NewClientInDomainAndAccount(domain string, account string, options ...ClientOption) (Client, error)
 }
 
-// cloud-config ini structure.
+// Config is the cloud-config ini structure.
 type Config struct {
 	APIUrl    string `yaml:"api-url"`
 	APIKey    string `yaml:"api-key"`
 	SecretKey string `yaml:"secret-key"`
 	VerifySSL string `yaml:"verify-ssl"`
+	ProjectID string `yaml:"project-id"`
 }
 
 type client struct {
@@ -108,7 +109,7 @@ func UnmarshalAllSecretConfigs(in []byte, out *[]SecretConfig) error {
 }
 
 // NewClientFromK8sSecret returns a client from a k8s secret.
-func NewClientFromK8sSecret(endpointSecret *corev1.Secret, clientConfig *corev1.ConfigMap) (Client, error) {
+func NewClientFromK8sSecret(endpointSecret *corev1.Secret, clientConfig *corev1.ConfigMap, options ...ClientOption) (Client, error) {
 	endpointSecretStrings := map[string]string{}
 	for k, v := range endpointSecret.Data {
 		endpointSecretStrings[k] = string(v)
@@ -118,11 +119,11 @@ func NewClientFromK8sSecret(endpointSecret *corev1.Secret, clientConfig *corev1.
 		return nil, err
 	}
 
-	return NewClientFromBytesConfig(bytes, clientConfig)
+	return NewClientFromBytesConfig(bytes, clientConfig, options...)
 }
 
 // NewClientFromBytesConfig returns a client from a bytes array that unmarshals to a yaml config.
-func NewClientFromBytesConfig(conf []byte, clientConfig *corev1.ConfigMap) (Client, error) {
+func NewClientFromBytesConfig(conf []byte, clientConfig *corev1.ConfigMap, options ...ClientOption) (Client, error) {
 	r := bytes.NewReader(conf)
 	dec := yaml.NewDecoder(r)
 	var config Config
@@ -130,11 +131,11 @@ func NewClientFromBytesConfig(conf []byte, clientConfig *corev1.ConfigMap) (Clie
 		return nil, err
 	}
 
-	return NewClientFromConf(config, clientConfig)
+	return NewClientFromConf(config, clientConfig, options...)
 }
 
 // NewClientFromYamlPath returns a client from a yaml config at path.
-func NewClientFromYamlPath(confPath string, secretName string) (Client, error) {
+func NewClientFromYamlPath(confPath string, secretName string, options ...ClientOption) (Client, error) {
 	content, err := os.ReadFile(confPath)
 	if err != nil {
 		return nil, err
@@ -155,11 +156,11 @@ func NewClientFromYamlPath(confPath string, secretName string) (Client, error) {
 		return nil, errors.Errorf("config with secret name %s not found", secretName)
 	}
 
-	return NewClientFromConf(conf, nil)
+	return NewClientFromConf(conf, nil, options...)
 }
 
 // NewClientFromConf creates a new Cloud Client form a map of strings to strings.
-func NewClientFromConf(conf Config, clientConfig *corev1.ConfigMap) (Client, error) {
+func NewClientFromConf(conf Config, clientConfig *corev1.ConfigMap, options ...ClientOption) (Client, error) {
 	cacheMutex.Lock()
 	defer cacheMutex.Unlock()
 
@@ -199,6 +200,24 @@ func NewClientFromConf(conf Config, clientConfig *corev1.ConfigMap) (Client, err
 			},
 		},
 	}
+
+	// Add project config if a ProjectID is defined in the client config.
+	if conf.ProjectID != "" {
+		user.Project = Project{
+			ID: conf.ProjectID,
+		}
+	}
+
+	oldUser := c.user
+	c.user = user
+	for _, fn := range options {
+		if err := fn(c); err != nil {
+			return nil, err
+		}
+	}
+	user = c.user
+	c.user = oldUser
+
 	if found, err := c.GetUserWithKeys(user); err != nil {
 		return nil, err
 	} else if !found {
@@ -212,10 +231,21 @@ func NewClientFromConf(conf Config, clientConfig *corev1.ConfigMap) (Client, err
 }
 
 // NewClientInDomainAndAccount returns a new client in the specified domain and account.
-func (c *client) NewClientInDomainAndAccount(domain string, account string) (Client, error) {
+func (c *client) NewClientInDomainAndAccount(domain string, account string, options ...ClientOption) (Client, error) {
 	user := &User{}
 	user.Account.Domain.Path = domain
 	user.Account.Name = account
+
+	oldUser := c.user
+	c.user = user
+	for _, fn := range options {
+		if err := fn(c); err != nil {
+			return nil, err
+		}
+	}
+	user = c.user
+	c.user = oldUser
+
 	if found, err := c.GetUserWithKeys(user); err != nil {
 		return nil, err
 	} else if !found {
@@ -286,3 +316,32 @@ func GetClientCacheTTL(clientConfig *corev1.ConfigMap) time.Duration {
 
 	return cacheTTL
 }
+
+// ClientOption can be passed to new client functions to set custom options.
+type ClientOption func(*client) error
+
+// WithProject takes either a project name or ID and sets the project ID parameter in the user object.
+func WithProject(project string) ClientOption {
+	return func(c *client) error {
+		if c == nil || c.user == nil {
+			return errors.New("cannot create client with nil user")
+		}
+
+		// project arg empty or project ID already set through cloud config.
+		if project == "" || c.user.Project.ID != "" {
+			return nil
+		}
+
+		if !cloudstack.IsID(project) {
+			id, _, err := c.cs.Project.GetProjectID(project)
+			if err != nil {
+				return err
+			}
+			project = id
+		}
+
+		c.user.Project.ID = project
+
+		return nil
+	}
+}
diff --git a/pkg/cloud/instance.go b/pkg/cloud/instance.go
index b939d593..8ab26318 100644
--- a/pkg/cloud/instance.go
+++ b/pkg/cloud/instance.go
@@ -73,7 +73,7 @@ func setMachineDataFromVMMetrics(vmResponse *cloudstack.VirtualMachinesMetric, c
 func (c *client) ResolveVMInstanceDetails(csMachine *infrav1.CloudStackMachine) error {
 	// Attempt to fetch by ID.
 	if csMachine.Spec.InstanceID != nil {
-		vmResp, count, err := c.cs.VirtualMachine.GetVirtualMachinesMetricByID(*csMachine.Spec.InstanceID)
+		vmResp, count, err := c.cs.VirtualMachine.GetVirtualMachinesMetricByID(*csMachine.Spec.InstanceID, cloudstack.WithProject(c.user.Project.ID))
 		if err != nil && !strings.Contains(strings.ToLower(err.Error()), "no match found") {
 			c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 
@@ -89,7 +89,7 @@ func (c *client) ResolveVMInstanceDetails(csMachine *infrav1.CloudStackMachine)
 
 	// Attempt fetch by name.
 	if csMachine.Name != "" {
-		vmResp, count, err := c.cs.VirtualMachine.GetVirtualMachinesMetricByName(csMachine.Name) // add opts usage
+		vmResp, count, err := c.cs.VirtualMachine.GetVirtualMachinesMetricByName(csMachine.Name, cloudstack.WithProject(c.user.Project.ID))
 		if err != nil && !strings.Contains(strings.ToLower(err.Error()), "no match") {
 			c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 
@@ -109,7 +109,7 @@ func (c *client) ResolveVMInstanceDetails(csMachine *infrav1.CloudStackMachine)
 // resolveServiceOffering attempts to look up the service offering of a CloudStackMachine by ID first and name second.
 func (c *client) resolveServiceOffering(csMachine *infrav1.CloudStackMachine, zoneID string) (offering *cloudstack.ServiceOffering, retErr error) {
 	if len(csMachine.Spec.Offering.ID) > 0 {
-		csOffering, count, err := c.cs.ServiceOffering.GetServiceOfferingByID(csMachine.Spec.Offering.ID)
+		csOffering, count, err := c.cs.ServiceOffering.GetServiceOfferingByID(csMachine.Spec.Offering.ID, cloudstack.WithProject(c.user.Project.ID))
 		if err != nil {
 			c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 
@@ -127,7 +127,7 @@ func (c *client) resolveServiceOffering(csMachine *infrav1.CloudStackMachine, zo
 
 		return csOffering, nil
 	}
-	csOffering, count, err := c.cs.ServiceOffering.GetServiceOfferingByName(csMachine.Spec.Offering.Name, cloudstack.WithZone(zoneID))
+	csOffering, count, err := c.cs.ServiceOffering.GetServiceOfferingByName(csMachine.Spec.Offering.Name, cloudstack.WithZone(zoneID), cloudstack.WithProject(c.user.Project.ID))
 	if err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 
@@ -147,7 +147,7 @@ func (c *client) resolveTemplate(
 	zoneID string,
 ) (templateID string, retErr error) {
 	if len(csMachine.Spec.Template.ID) > 0 {
-		csTemplate, count, err := c.cs.Template.GetTemplateByID(csMachine.Spec.Template.ID, "executable")
+		csTemplate, count, err := c.cs.Template.GetTemplateByID(csMachine.Spec.Template.ID, "executable", cloudstack.WithProject(c.user.Project.ID))
 		if err != nil {
 			c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 
@@ -165,15 +165,17 @@ func (c *client) resolveTemplate(
 
 		return csMachine.Spec.Template.ID, nil
 	}
-	templateID, count, err := c.cs.Template.GetTemplateID(csMachine.Spec.Template.Name, "executable", zoneID, func(_ *cloudstack.CloudStackClient, i interface{}) error {
-		v, ok := i.(*cloudstack.ListTemplatesParams)
-		if !ok {
-			return fmt.Errorf("expected a cloudstack.ListTemplatesParams but got a %T", i)
-		}
-		v.SetShowunique(true)
+	templateID, count, err := c.cs.Template.GetTemplateID(csMachine.Spec.Template.Name, "executable", zoneID,
+		cloudstack.WithProject(c.user.Project.ID),
+		func(_ *cloudstack.CloudStackClient, i interface{}) error {
+			v, ok := i.(*cloudstack.ListTemplatesParams)
+			if !ok {
+				return fmt.Errorf("expected a cloudstack.ListTemplatesParams but got a %T", i)
+			}
+			v.SetShowunique(true)
 
-		return nil
-	})
+			return nil
+		})
 	if err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 
@@ -196,7 +198,7 @@ func (c *client) resolveDiskOffering(csMachine *infrav1.CloudStackMachine, zoneI
 	}
 	diskOfferingID = csMachine.Spec.DiskOffering.ID
 	if len(csMachine.Spec.DiskOffering.Name) > 0 {
-		diskID, count, err := c.cs.DiskOffering.GetDiskOfferingID(csMachine.Spec.DiskOffering.Name, cloudstack.WithZone(zoneID))
+		diskID, count, err := c.cs.DiskOffering.GetDiskOfferingID(csMachine.Spec.DiskOffering.Name, cloudstack.WithZone(zoneID), cloudstack.WithProject(c.user.Project.ID))
 		if err != nil {
 			c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 
@@ -224,7 +226,7 @@ func (c *client) resolveDiskOffering(csMachine *infrav1.CloudStackMachine, zoneI
 }
 
 func verifyDiskoffering(csMachine *infrav1.CloudStackMachine, c *client, diskOfferingID string, retErr error) (string, error) {
-	csDiskOffering, count, err := c.cs.DiskOffering.GetDiskOfferingByID(diskOfferingID)
+	csDiskOffering, count, err := c.cs.DiskOffering.GetDiskOfferingByID(diskOfferingID, cloudstack.WithProject(c.user.Project.ID))
 	if err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 
@@ -302,6 +304,35 @@ func (c *client) checkDomainLimits(offering *cloudstack.ServiceOffering) error {
 	return nil
 }
 
+// CheckProjectLimits Checks the project's limit of VM, CPU & Memory.
+func (c *client) CheckProjectLimits(offering *cloudstack.ServiceOffering) error {
+	if c.user.Project.ID == "" {
+		return nil
+	}
+
+	if c.user.Project.CPUAvailable != LimitUnlimited {
+		cpuAvailable, err := strconv.ParseInt(c.user.Project.CPUAvailable, 10, 0)
+		if err == nil && int64(offering.Cpunumber) > cpuAvailable {
+			return fmt.Errorf("CPU available (%d) in project can't fulfil the requirement: %d", cpuAvailable, offering.Cpunumber)
+		}
+	}
+
+	if c.user.Project.MemoryAvailable != LimitUnlimited {
+		memoryAvailable, err := strconv.ParseInt(c.user.Project.MemoryAvailable, 10, 0)
+		if err == nil && int64(offering.Memory) > memoryAvailable {
+			return fmt.Errorf("memory available (%d) in project can't fulfil the requirement: %d", memoryAvailable, offering.Memory)
+		}
+	}
+
+	if c.user.Project.VMAvailable != LimitUnlimited {
+		vmAvailable, err := strconv.ParseInt(c.user.Project.VMAvailable, 10, 0)
+		if err == nil && vmAvailable < 1 {
+			return fmt.Errorf("VM Limit in project has reached it's maximum value")
+		}
+	}
+	return nil
+}
+
 // checkLimits will check the account & domain limits.
 func (c *client) checkLimits(
 	offering *cloudstack.ServiceOffering,
@@ -320,6 +351,11 @@ func (c *client) checkLimits(
 		return err
 	}
 
+	err = c.CheckProjectLimits(offering)
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
 
@@ -346,6 +382,7 @@ func (c *client) deployVM(
 	setIfNotEmpty(csMachine.Name, p.SetName)
 	setIfNotEmpty(capiMachine.Name, p.SetDisplayname)
 	setIfNotEmpty(diskOfferingID, p.SetDiskofferingid)
+	setIfNotEmpty(c.user.Project.ID, p.SetProjectid)
 	if csMachine.Spec.DiskOffering != nil {
 		setIntIfPositive(csMachine.Spec.DiskOffering.CustomSize, p.SetSize)
 	}
@@ -378,7 +415,7 @@ func (c *client) deployVM(
 		// CloudStack may have created the VM even though it reported an error. We attempt to
 		// retrieve the VM so we can populate the CloudStackMachine for the user to manually
 		// clean up.
-		vm, findErr := findVirtualMachine(c.cs.VirtualMachine, templateID, fd, csMachine)
+		vm, findErr := findVirtualMachine(c.cs.VirtualMachine, c.user.Project.ID, templateID, fd, csMachine)
 		if findErr != nil {
 			if errors.Is(findErr, ErrNotFound) {
 				// We didn't find a VM so return the original error.
@@ -439,6 +476,7 @@ func (c *client) GetOrCreateVMInstance(
 // domain zone and failure domain network. If no virtual machine is found it returns nil, ErrNotFound.
 func findVirtualMachine(
 	client cloudstack.VirtualMachineServiceIface,
+	projectID string,
 	templateID string,
 	failureDomain *infrav1.CloudStackFailureDomain,
 	machine *infrav1.CloudStackMachine,
@@ -448,6 +486,7 @@ func findVirtualMachine(
 	params.SetZoneid(failureDomain.Spec.Zone.ID)
 	params.SetNetworkid(failureDomain.Spec.Zone.Network.ID)
 	params.SetName(machine.Name)
+	setIfNotEmpty(projectID, params.SetProjectid)
 
 	response, err := client.ListVirtualMachines(params)
 	if err != nil {
@@ -518,6 +557,7 @@ func (c *client) listVMInstanceDatadiskVolumeIDs(instanceID string) ([]string, e
 	// - https://github.com/apache/cloudstack/blob/b69cc0272d48f0aea7353627d760c27c284dad84/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java#L524
 	// - https://github.com/kubernetes-sigs/cluster-api-provider-cloudstack/issues/389
 	p.SetKeyword("DATA-")
+	setIfNotEmpty(c.user.Project.ID, p.SetProjectid)
 
 	listVolResp, err := c.csAsync.Volume.ListVolumes(p)
 	if err != nil {
diff --git a/pkg/cloud/instance_test.go b/pkg/cloud/instance_test.go
index b5dd8bb0..118e79ed 100644
--- a/pkg/cloud/instance_test.go
+++ b/pkg/cloud/instance_test.go
@@ -76,42 +76,42 @@ var _ = Describe("Instance", func() {
 
 	Context("when fetching a VM instance", func() {
 		It("Handles an unknown error when fetching by ID", func() {
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).Return(nil, -1, unknownError)
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).Return(nil, -1, unknownError)
 			Ω(client.ResolveVMInstanceDetails(dummies.CSMachine1)).To(MatchError(unknownErrorMessage))
 		})
 
 		It("Handles finding more than one VM instance by ID", func() {
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).Return(nil, 2, nil)
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).Return(nil, 2, nil)
 			Ω(client.ResolveVMInstanceDetails(dummies.CSMachine1)).
 				Should(MatchError("found more than one VM Instance with ID " + *dummies.CSMachine1.Spec.InstanceID))
 		})
 
 		It("sets dummies.CSMachine1 spec and status values when VM instance found by ID", func() {
 			vmsResp := &cloudstack.VirtualMachinesMetric{Id: *dummies.CSMachine1.Spec.InstanceID}
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).Return(vmsResp, 1, nil)
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).Return(vmsResp, 1, nil)
 			Ω(client.ResolveVMInstanceDetails(dummies.CSMachine1)).Should(Succeed())
 			Ω(dummies.CSMachine1.Spec.ProviderID).Should(Equal(ptr.To("cloudstack:///" + vmsResp.Id)))
 			Ω(dummies.CSMachine1.Spec.InstanceID).Should(Equal(ptr.To(vmsResp.Id)))
 		})
 
 		It("handles an unknown error when fetching by name", func() {
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).Return(nil, -1, notFoundError)
-			vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name).Return(nil, -1, unknownError)
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).Return(nil, -1, notFoundError)
+			vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name, gomock.Any()).Return(nil, -1, unknownError)
 
 			Ω(client.ResolveVMInstanceDetails(dummies.CSMachine1)).Should(MatchError(unknownErrorMessage))
 		})
 
 		It("handles finding more than one VM instance by Name", func() {
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).Return(nil, -1, notFoundError)
-			vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name).Return(nil, 2, nil)
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).Return(nil, -1, notFoundError)
+			vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name, gomock.Any()).Return(nil, 2, nil)
 
 			Ω(client.ResolveVMInstanceDetails(dummies.CSMachine1)).Should(
 				MatchError("found more than one VM Instance with name " + dummies.CSMachine1.Name))
 		})
 
 		It("sets dummies.CSMachine1 spec and status values when VM instance found by Name", func() {
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).Return(nil, -1, notFoundError)
-			vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name).
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).Return(nil, -1, notFoundError)
+			vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name, gomock.Any()).
 				Return(&cloudstack.VirtualMachinesMetric{Id: *dummies.CSMachine1.Spec.InstanceID}, -1, nil)
 
 			Ω(client.ResolveVMInstanceDetails(dummies.CSMachine1)).Should(Succeed())
@@ -125,19 +125,19 @@ var _ = Describe("Instance", func() {
 		vmMetricResp := &cloudstack.VirtualMachinesMetric{}
 
 		expectVMNotFound := func() {
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).Return(nil, -1, notFoundError)
-			vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name).Return(nil, -1, notFoundError)
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).Return(nil, -1, notFoundError)
+			vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name, gomock.Any()).Return(nil, -1, notFoundError)
 		}
 
 		It("doesn't re-create if one already exists.", func() {
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).Return(vmMetricResp, -1, nil)
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).Return(vmMetricResp, -1, nil)
 			Ω(client.GetOrCreateVMInstance(
 				dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")).
 				Should(Succeed())
 		})
 
 		It("returns unknown error while fetching VM instance", func() {
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).Return(nil, -1, unknownError)
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).Return(nil, -1, unknownError)
 			Ω(client.GetOrCreateVMInstance(
 				dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")).
 				Should(MatchError(unknownErrorMessage))
@@ -170,7 +170,7 @@ var _ = Describe("Instance", func() {
 					Id:   dummies.CSMachine1.Spec.Offering.ID,
 					Name: dummies.CSMachine1.Spec.Offering.Name,
 				}, 1, nil)
-			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any()).
+			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any(), gomock.Any()).
 				Return("", -1, unknownError)
 			Ω(client.GetOrCreateVMInstance(
 				dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")).
@@ -185,7 +185,7 @@ var _ = Describe("Instance", func() {
 					Id:   dummies.CSMachine1.Spec.Offering.ID,
 					Name: dummies.CSMachine1.Spec.Offering.Name,
 				}, 1, nil)
-			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any()).Return("", 2, nil)
+			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any(), gomock.Any()).Return("", 2, nil)
 			Ω(client.GetOrCreateVMInstance(
 				dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")).
 				ShouldNot(Succeed())
@@ -199,7 +199,7 @@ var _ = Describe("Instance", func() {
 					Id:   dummies.CSMachine1.Spec.Offering.ID,
 					Name: dummies.CSMachine1.Spec.Offering.Name,
 				}, 1, nil)
-			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any()).Return(dummies.CSMachine1.Spec.Template.ID, 1, nil)
+			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any(), gomock.Any()).Return(dummies.CSMachine1.Spec.Template.ID, 1, nil)
 			dos.EXPECT().GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).Return(diskOfferingFakeID, 2, nil)
 			Ω(client.GetOrCreateVMInstance(
 				dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")).
@@ -213,9 +213,9 @@ var _ = Describe("Instance", func() {
 					Id:   dummies.CSMachine1.Spec.Offering.ID,
 					Name: dummies.CSMachine1.Spec.Offering.Name,
 				}, 1, nil)
-			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any()).Return(dummies.CSMachine1.Spec.Template.ID, 1, nil)
+			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any(), gomock.Any()).Return(dummies.CSMachine1.Spec.Template.ID, 1, nil)
 			dos.EXPECT().GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).Return(diskOfferingFakeID, 1, nil)
-			dos.EXPECT().GetDiskOfferingByID(diskOfferingFakeID).Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, unknownError)
+			dos.EXPECT().GetDiskOfferingByID(diskOfferingFakeID, gomock.Any()).Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, unknownError)
 			Ω(client.GetOrCreateVMInstance(
 				dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")).
 				ShouldNot(Succeed())
@@ -229,9 +229,9 @@ var _ = Describe("Instance", func() {
 					Id:   dummies.CSMachine1.Spec.Offering.ID,
 					Name: dummies.CSMachine1.Spec.Offering.Name,
 				}, 1, nil)
-			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any()).Return(dummies.CSMachine1.Spec.Template.ID, 1, nil)
+			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any(), gomock.Any()).Return(dummies.CSMachine1.Spec.Template.ID, 1, nil)
 			dos.EXPECT().GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).Return(diskOfferingFakeID, 1, nil)
-			dos.EXPECT().GetDiskOfferingByID(diskOfferingFakeID).Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil)
+			dos.EXPECT().GetDiskOfferingByID(diskOfferingFakeID, gomock.Any()).Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil)
 			Ω(client.GetOrCreateVMInstance(
 				dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")).
 				ShouldNot(Succeed())
@@ -247,9 +247,9 @@ var _ = Describe("Instance", func() {
 					Cpunumber: 1,
 					Memory:    1024,
 				}, 1, nil)
-			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any()).Return(dummies.CSMachine1.Spec.Template.ID, 1, nil)
+			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any(), gomock.Any()).Return(dummies.CSMachine1.Spec.Template.ID, 1, nil)
 			dos.EXPECT().GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).Return(diskOfferingFakeID, 1, nil)
-			dos.EXPECT().GetDiskOfferingByID(diskOfferingFakeID).Return(&cloudstack.DiskOffering{Iscustomized: true}, 1, nil)
+			dos.EXPECT().GetDiskOfferingByID(diskOfferingFakeID, gomock.Any()).Return(&cloudstack.DiskOffering{Iscustomized: true}, 1, nil)
 			Ω(client.GetOrCreateVMInstance(
 				dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")).
 				ShouldNot(Succeed())
@@ -277,6 +277,12 @@ var _ = Describe("Instance", func() {
 						MemoryAvailable: "2048",
 						VMAvailable:     "20",
 					},
+					Project: cloud.Project{
+						ID:              "123",
+						CPUAvailable:    "20",
+						MemoryAvailable: "2048",
+						VMAvailable:     "20",
+					},
 				}
 				c := cloud.NewClientFromCSAPIClient(mockClient, user)
 				Ω(c.GetOrCreateVMInstance(
@@ -305,6 +311,12 @@ var _ = Describe("Instance", func() {
 						MemoryAvailable: "2048",
 						VMAvailable:     "20",
 					},
+					Project: cloud.Project{
+						ID:              "123",
+						CPUAvailable:    "20",
+						MemoryAvailable: "2048",
+						VMAvailable:     "20",
+					},
 				}
 				c := cloud.NewClientFromCSAPIClient(mockClient, user)
 				Ω(c.GetOrCreateVMInstance(
@@ -312,6 +324,40 @@ var _ = Describe("Instance", func() {
 					Should(MatchError(MatchRegexp("CPU available .* in domain can't fulfil the requirement:.*")))
 			})
 
+			It("returns errors when there are not enough available CPU in project", func() {
+				expectVMNotFound()
+				dummies.CSMachine1.Spec.DiskOffering.CustomSize = 0
+				sos.EXPECT().GetServiceOfferingByName(dummies.CSMachine1.Spec.Offering.Name, gomock.Any()).
+					Return(&cloudstack.ServiceOffering{
+						Id:        dummies.CSMachine1.Spec.Offering.ID,
+						Name:      dummies.CSMachine1.Spec.Offering.Name,
+						Cpunumber: 2,
+						Memory:    1024,
+					}, 1, nil)
+				user := &cloud.User{
+					Account: cloud.Account{
+						Domain: cloud.Domain{
+							CPUAvailable:    "20",
+							MemoryAvailable: "2048",
+							VMAvailable:     "20",
+						},
+						CPUAvailable:    "20",
+						MemoryAvailable: "2048",
+						VMAvailable:     "20",
+					},
+					Project: cloud.Project{
+						ID:              "123",
+						CPUAvailable:    "1",
+						MemoryAvailable: "2048",
+						VMAvailable:     "20",
+					},
+				}
+				c := cloud.NewClientFromCSAPIClient(mockClient, user)
+				Ω(c.GetOrCreateVMInstance(
+					dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")).
+					Should(MatchError(MatchRegexp("CPU available .* in project can't fulfil the requirement:.*")))
+			})
+
 			It("returns errors when there is not enough available memory in account", func() {
 				expectVMNotFound()
 				dummies.CSMachine1.Spec.DiskOffering.CustomSize = 0
@@ -333,6 +379,12 @@ var _ = Describe("Instance", func() {
 						MemoryAvailable: "512",
 						VMAvailable:     "20",
 					},
+					Project: cloud.Project{
+						ID:              "123",
+						CPUAvailable:    "1",
+						MemoryAvailable: "2048",
+						VMAvailable:     "20",
+					},
 				}
 				c := cloud.NewClientFromCSAPIClient(mockClient, user)
 				Ω(c.GetOrCreateVMInstance(
@@ -361,6 +413,12 @@ var _ = Describe("Instance", func() {
 						MemoryAvailable: "2048",
 						VMAvailable:     "20",
 					},
+					Project: cloud.Project{
+						ID:              "123",
+						CPUAvailable:    "1",
+						MemoryAvailable: "2048",
+						VMAvailable:     "20",
+					},
 				}
 				c := cloud.NewClientFromCSAPIClient(mockClient, user)
 				Ω(c.GetOrCreateVMInstance(
@@ -368,6 +426,40 @@ var _ = Describe("Instance", func() {
 					Should(MatchError(MatchRegexp("memory available .* in domain can't fulfil the requirement:.*")))
 			})
 
+			It("returns errors when there is not enough available memory in project", func() {
+				expectVMNotFound()
+				dummies.CSMachine1.Spec.DiskOffering.CustomSize = 0
+				sos.EXPECT().GetServiceOfferingByName(dummies.CSMachine1.Spec.Offering.Name, gomock.Any()).
+					Return(&cloudstack.ServiceOffering{
+						Id:        dummies.CSMachine1.Spec.Offering.ID,
+						Name:      dummies.CSMachine1.Spec.Offering.Name,
+						Cpunumber: 2,
+						Memory:    1024,
+					}, 1, nil)
+				user := &cloud.User{
+					Account: cloud.Account{
+						Domain: cloud.Domain{
+							CPUAvailable:    "20",
+							MemoryAvailable: "2048",
+							VMAvailable:     "20",
+						},
+						CPUAvailable:    "20",
+						MemoryAvailable: "2048",
+						VMAvailable:     "20",
+					},
+					Project: cloud.Project{
+						ID:              "123",
+						CPUAvailable:    "20",
+						MemoryAvailable: "512",
+						VMAvailable:     "20",
+					},
+				}
+				c := cloud.NewClientFromCSAPIClient(mockClient, user)
+				Ω(c.GetOrCreateVMInstance(
+					dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")).
+					Should(MatchError(MatchRegexp("memory available .* in project can't fulfil the requirement:.*")))
+			})
+
 			It("returns errors when there is not enough available VM limit in account", func() {
 				expectVMNotFound()
 				dummies.CSMachine1.Spec.DiskOffering.CustomSize = 0
@@ -389,6 +481,12 @@ var _ = Describe("Instance", func() {
 						MemoryAvailable: "2048",
 						VMAvailable:     "0",
 					},
+					Project: cloud.Project{
+						ID:              "123",
+						CPUAvailable:    "20",
+						MemoryAvailable: "512",
+						VMAvailable:     "20",
+					},
 				}
 				c := cloud.NewClientFromCSAPIClient(mockClient, user)
 				Ω(c.GetOrCreateVMInstance(
@@ -417,6 +515,12 @@ var _ = Describe("Instance", func() {
 						MemoryAvailable: "2048",
 						VMAvailable:     "10",
 					},
+					Project: cloud.Project{
+						ID:              "123",
+						CPUAvailable:    "20",
+						MemoryAvailable: "512",
+						VMAvailable:     "20",
+					},
 				}
 				c := cloud.NewClientFromCSAPIClient(mockClient, user)
 				Ω(c.GetOrCreateVMInstance(
@@ -425,6 +529,40 @@ var _ = Describe("Instance", func() {
 			})
 		})
 
+		It("returns errors when there is not enough available VM limit in project", func() {
+			expectVMNotFound()
+			dummies.CSMachine1.Spec.DiskOffering.CustomSize = 0
+			sos.EXPECT().GetServiceOfferingByName(dummies.CSMachine1.Spec.Offering.Name, gomock.Any()).
+				Return(&cloudstack.ServiceOffering{
+					Id:        dummies.CSMachine1.Spec.Offering.ID,
+					Name:      dummies.CSMachine1.Spec.Offering.Name,
+					Cpunumber: 2,
+					Memory:    1024,
+				}, 1, nil)
+			user := &cloud.User{
+				Account: cloud.Account{
+					Domain: cloud.Domain{
+						CPUAvailable:    "20",
+						MemoryAvailable: "2048",
+						VMAvailable:     "10",
+					},
+					CPUAvailable:    "20",
+					MemoryAvailable: "2048",
+					VMAvailable:     "10",
+				},
+				Project: cloud.Project{
+					ID:              "123",
+					CPUAvailable:    "20",
+					MemoryAvailable: "2048",
+					VMAvailable:     "0",
+				},
+			}
+			c := cloud.NewClientFromCSAPIClient(mockClient, user)
+			Ω(c.GetOrCreateVMInstance(
+				dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")).
+				Should(MatchError("VM Limit in project has reached it's maximum value"))
+		})
+
 		It("handles deployment errors", func() {
 			expectVMNotFound()
 			sos.EXPECT().GetServiceOfferingByName(dummies.CSMachine1.Spec.Offering.Name, gomock.Any()).
@@ -434,11 +572,11 @@ var _ = Describe("Instance", func() {
 					Cpunumber: 1,
 					Memory:    1024,
 				}, 1, nil)
-			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any()).
+			ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any(), gomock.Any()).
 				Return(templateFakeID, 1, nil)
 			dos.EXPECT().GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).
 				Return(diskOfferingFakeID, 1, nil)
-			dos.EXPECT().GetDiskOfferingByID(diskOfferingFakeID).
+			dos.EXPECT().GetDiskOfferingByID(diskOfferingFakeID, gomock.Any()).
 				Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil)
 			vms.EXPECT().NewDeployVirtualMachineParams(offeringFakeID, templateFakeID, dummies.Zone1.ID).
 				Return(&cloudstack.DeployVirtualMachineParams{})
@@ -453,12 +591,12 @@ var _ = Describe("Instance", func() {
 		Context("when using UUIDs and/or names to locate service offerings and templates", func() {
 			BeforeEach(func() {
 				gomock.InOrder(
-					vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).
+					vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).
 						Return(nil, -1, notFoundError),
-					vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).
+					vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).
 						Return(&cloudstack.VirtualMachinesMetric{}, 1, nil))
 
-				vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name).Return(nil, -1, notFoundError)
+				vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name, gomock.Any()).Return(nil, -1, notFoundError)
 			})
 
 			ActionAndAssert := func() {
@@ -504,8 +642,8 @@ var _ = Describe("Instance", func() {
 					Memory:    1024,
 				}, 1, nil)
 				dos.EXPECT().GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).Return(diskOfferingFakeID, 1, nil)
-				dos.EXPECT().GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID).Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil)
-				ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any()).
+				dos.EXPECT().GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID, gomock.Any()).Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil)
+				ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any(), gomock.Any()).
 					Return(templateFakeID, 1, nil)
 
 				ActionAndAssert()
@@ -523,7 +661,7 @@ var _ = Describe("Instance", func() {
 					Cpunumber: 1,
 					Memory:    1024,
 				}, 1, nil)
-				ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any()).
+				ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any(), gomock.Any()).
 					Return(templateFakeID, 1, nil)
 
 				ActionAndAssert()
@@ -536,15 +674,15 @@ var _ = Describe("Instance", func() {
 				dummies.CSMachine1.Spec.Offering.Name = ""
 				dummies.CSMachine1.Spec.Template.Name = templateName
 
-				sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID).Return(&cloudstack.ServiceOffering{
+				sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID, gomock.Any()).Return(&cloudstack.ServiceOffering{
 					Id:        offeringFakeID,
 					Cpunumber: 1,
 					Memory:    1024,
 				}, 1, nil)
-				ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any()).
+				ts.EXPECT().GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any(), gomock.Any()).
 					Return(templateFakeID, 1, nil)
 				dos.EXPECT().GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).Return(diskOfferingFakeID, 1, nil)
-				dos.EXPECT().GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID).Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil)
+				dos.EXPECT().GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID, gomock.Any()).Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil)
 
 				ActionAndAssert()
 			})
@@ -562,9 +700,9 @@ var _ = Describe("Instance", func() {
 					Memory:    1024,
 				}, 1, nil)
 
-				ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter).Return(&cloudstack.Template{Name: ""}, 1, nil)
+				ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter, gomock.Any()).Return(&cloudstack.Template{Name: ""}, 1, nil)
 				dos.EXPECT().GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).Return(diskOfferingFakeID, 1, nil)
-				dos.EXPECT().GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID).Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil)
+				dos.EXPECT().GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID, gomock.Any()).Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil)
 
 				ActionAndAssert()
 			})
@@ -576,7 +714,7 @@ var _ = Describe("Instance", func() {
 				dummies.CSMachine1.Spec.Offering.Name = ""
 				dummies.CSMachine1.Spec.Template.Name = ""
 
-				sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID).
+				sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID, gomock.Any()).
 					Return(&cloudstack.ServiceOffering{
 						Id:        offeringFakeID,
 						Cpunumber: 1,
@@ -584,9 +722,9 @@ var _ = Describe("Instance", func() {
 					}, 1, nil)
 				dos.EXPECT().GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).
 					Return(diskOfferingFakeID, 1, nil)
-				dos.EXPECT().GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID).
+				dos.EXPECT().GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID, gomock.Any()).
 					Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil)
-				ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter).
+				ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter, gomock.Any()).
 					Return(&cloudstack.Template{Name: templateName}, 1, nil)
 
 				ActionAndAssert()
@@ -599,15 +737,15 @@ var _ = Describe("Instance", func() {
 				dummies.CSMachine1.Spec.Offering.Name = offeringName
 				dummies.CSMachine1.Spec.Template.Name = templateName
 
-				sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID).Return(&cloudstack.ServiceOffering{
+				sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID, gomock.Any()).Return(&cloudstack.ServiceOffering{
 					Id:        dummies.CSMachine1.Spec.Offering.ID,
 					Name:      dummies.CSMachine1.Spec.Offering.Name,
 					Cpunumber: 1,
 					Memory:    1024,
 				}, 1, nil)
-				ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter).Return(&cloudstack.Template{Name: templateName}, 1, nil)
+				ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter, gomock.Any()).Return(&cloudstack.Template{Name: templateName}, 1, nil)
 				dos.EXPECT().GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).Return(diskOfferingFakeID, 1, nil)
-				dos.EXPECT().GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID).Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil)
+				dos.EXPECT().GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID, gomock.Any()).Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil)
 
 				ActionAndAssert()
 			})
@@ -615,9 +753,9 @@ var _ = Describe("Instance", func() {
 
 		Context("when using both UUIDs and names to locate service offerings and templates", func() {
 			BeforeEach(func() {
-				vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).
+				vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).
 					Return(nil, -1, notFoundError)
-				vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name).Return(nil, -1, notFoundError)
+				vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name, gomock.Any()).Return(nil, -1, notFoundError)
 			})
 
 			It("works with Id and name both provided, offering name mismatch", func() {
@@ -626,7 +764,7 @@ var _ = Describe("Instance", func() {
 				dummies.CSMachine1.Spec.Offering.Name = offeringName
 				dummies.CSMachine1.Spec.Template.Name = templateName
 
-				sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID).Return(&cloudstack.ServiceOffering{Name: "offering-not-match"}, 1, nil)
+				sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID, gomock.Any()).Return(&cloudstack.ServiceOffering{Name: "offering-not-match"}, 1, nil)
 				requiredRegexp := "offering name %s does not match name %s returned using UUID %s"
 				Ω(client.GetOrCreateVMInstance(
 					dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")).
@@ -639,8 +777,8 @@ var _ = Describe("Instance", func() {
 				dummies.CSMachine1.Spec.Offering.Name = offeringName
 				dummies.CSMachine1.Spec.Template.Name = templateName
 
-				sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID).Return(&cloudstack.ServiceOffering{Name: offeringName}, 1, nil)
-				ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter).Return(&cloudstack.Template{Name: "template-not-match"}, 1, nil)
+				sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID, gomock.Any()).Return(&cloudstack.ServiceOffering{Name: offeringName}, 1, nil)
+				ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter, gomock.Any()).Return(&cloudstack.Template{Name: "template-not-match"}, 1, nil)
 				requiredRegexp := "template name %s does not match name %s returned using UUID %s"
 				Ω(client.GetOrCreateVMInstance(
 					dummies.CSMachine1, dummies.CAPIMachine, dummies.CSFailureDomain1, dummies.CSAffinityGroup, "")).
@@ -655,8 +793,8 @@ var _ = Describe("Instance", func() {
 				dummies.CSMachine1.Spec.Template.Name = templateName
 				dummies.CSMachine1.Spec.DiskOffering.Name = "diskoffering"
 
-				sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID).Return(&cloudstack.ServiceOffering{Name: offeringName}, 1, nil)
-				ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter).Return(&cloudstack.Template{Name: templateName}, 1, nil)
+				sos.EXPECT().GetServiceOfferingByID(dummies.CSMachine1.Spec.Offering.ID, gomock.Any()).Return(&cloudstack.ServiceOffering{Name: offeringName}, 1, nil)
+				ts.EXPECT().GetTemplateByID(dummies.CSMachine1.Spec.Template.ID, executableFilter, gomock.Any()).Return(&cloudstack.Template{Name: templateName}, 1, nil)
 				dos.EXPECT().GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).Return(diskOfferingFakeID+"-not-match", 1, nil)
 				requiredRegexp := "diskOffering ID %s does not match ID %s returned using name %s"
 				Ω(client.GetOrCreateVMInstance(
@@ -674,13 +812,13 @@ var _ = Describe("Instance", func() {
 			dummies.CSMachine1.Spec.UncompressedUserData = ptr.To(true)
 
 			vms.EXPECT().
-				GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).
+				GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).
 				Return(nil, -1, notFoundError)
 			vms.EXPECT().
-				GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).
+				GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).
 				Return(&cloudstack.VirtualMachinesMetric{}, 1, nil)
 			vms.EXPECT().
-				GetVirtualMachinesMetricByName(dummies.CSMachine1.Name).
+				GetVirtualMachinesMetricByName(dummies.CSMachine1.Name, gomock.Any()).
 				Return(nil, -1, notFoundError)
 
 			sos.EXPECT().
@@ -694,7 +832,7 @@ var _ = Describe("Instance", func() {
 				GetDiskOfferingID(dummies.CSMachine1.Spec.DiskOffering.Name, gomock.Any()).
 				Return(diskOfferingFakeID, 1, nil)
 			dos.EXPECT().
-				GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID).
+				GetDiskOfferingByID(dummies.CSMachine1.Spec.DiskOffering.ID, gomock.Any()).
 				Return(&cloudstack.DiskOffering{Iscustomized: false}, 1, nil)
 			ts.EXPECT().
 				GetTemplateID(dummies.CSMachine1.Spec.Template.Name, executableFilter, dummies.Zone1.ID, gomock.Any()).
@@ -782,8 +920,8 @@ var _ = Describe("Instance", func() {
 			vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, nil)
 			vs.EXPECT().NewListVolumesParams().Return(listVolumesParams)
 			vs.EXPECT().ListVolumes(listVolumesParams).Return(listVolumesResponse, nil)
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).Return(nil, -1, notFoundError)
-			vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name).Return(nil, -1, notFoundError)
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).Return(nil, -1, notFoundError)
+			vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name, gomock.Any()).Return(nil, -1, notFoundError)
 			Ω(client.DestroyVMInstance(dummies.CSMachine1)).
 				Should(Succeed())
 		})
@@ -797,7 +935,7 @@ var _ = Describe("Instance", func() {
 			vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, nil)
 			vs.EXPECT().NewListVolumesParams().Return(listVolumesParams)
 			vs.EXPECT().ListVolumes(listVolumesParams).Return(listVolumesResponse, nil)
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).
 				Return(&cloudstack.VirtualMachinesMetric{
 					State: "Expunging",
 				}, 1, nil)
@@ -814,7 +952,7 @@ var _ = Describe("Instance", func() {
 			vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, nil)
 			vs.EXPECT().NewListVolumesParams().Return(listVolumesParams)
 			vs.EXPECT().ListVolumes(listVolumesParams).Return(listVolumesResponse, nil)
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).
 				Return(&cloudstack.VirtualMachinesMetric{
 					State: "Expunged",
 				}, 1, nil)
@@ -831,7 +969,7 @@ var _ = Describe("Instance", func() {
 			vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, nil)
 			vs.EXPECT().NewListVolumesParams().Return(listVolumesParams)
 			vs.EXPECT().ListVolumes(listVolumesParams).Return(listVolumesResponse, nil)
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).
 				Return(&cloudstack.VirtualMachinesMetric{
 					State: "Stopping",
 				}, 1, nil)
@@ -859,7 +997,7 @@ var _ = Describe("Instance", func() {
 				})
 			vs.EXPECT().NewListVolumesParams().Times(0)
 			vs.EXPECT().ListVolumes(gomock.Any()).Times(0)
-			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).
+			vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID, gomock.Any()).
 				Return(&cloudstack.VirtualMachinesMetric{
 					State: "Expunged",
 				}, 1, nil)
diff --git a/pkg/cloud/isolated_network.go b/pkg/cloud/isolated_network.go
index ed7db51d..586ad6b9 100644
--- a/pkg/cloud/isolated_network.go
+++ b/pkg/cloud/isolated_network.go
@@ -89,6 +89,7 @@ func (c *client) AssociatePublicIPAddress(
 	p := c.cs.Address.NewAssociateIpAddressParams()
 	p.SetIpaddress(publicAddress.Ipaddress)
 	p.SetNetworkid(isoNet.Spec.ID)
+	setIfNotEmpty(c.user.Project.ID, p.SetProjectid)
 	if _, err := c.cs.Address.AssociateIpAddress(p); err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 
@@ -113,9 +114,8 @@ func (c *client) CreateIsolatedNetwork(fd *infrav1.CloudStackFailureDomain, isoN
 	// Do isolated network creation.
 	p := c.cs.Network.NewCreateNetworkParams(isoNet.Spec.Name, offeringID, fd.Spec.Zone.ID)
 	p.SetDisplaytext(isoNet.Spec.Name)
-	if isoNet.Spec.Domain != "" {
-		p.SetNetworkdomain(isoNet.Spec.Domain)
-	}
+	setIfNotEmpty(c.user.Project.ID, p.SetProjectid)
+	setIfNotEmpty(isoNet.Spec.Domain, p.SetNetworkdomain)
 	if isoNet.Spec.CIDR != "" {
 		m, err := parseCIDR(isoNet.Spec.CIDR)
 		if err != nil {
@@ -174,6 +174,7 @@ func (c *client) GetPublicIP(
 	p.SetAllocatedonly(false)
 	p.SetZoneid(fd.Spec.Zone.ID)
 	setIfNotEmpty(desiredIP, p.SetIpaddress)
+	setIfNotEmpty(c.user.Project.ID, p.SetProjectid)
 	publicAddresses, err := c.cs.Address.ListPublicIpAddresses(p)
 	if err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
@@ -199,7 +200,7 @@ func (c *client) GetPublicIP(
 
 // GetIsolatedNetwork gets an isolated network in the relevant Zone.
 func (c *client) GetIsolatedNetwork(isoNet *infrav1.CloudStackIsolatedNetwork) (retErr error) {
-	netDetails, count, err := c.cs.Network.GetNetworkByName(isoNet.Spec.Name)
+	netDetails, count, err := c.cs.Network.GetNetworkByName(isoNet.Spec.Name, cloudstack.WithProject(c.user.Project.ID))
 	if err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 		retErr = multierror.Append(retErr, errors.Wrapf(err, "could not get Network ID from %s", isoNet.Spec.Name))
@@ -213,7 +214,7 @@ func (c *client) GetIsolatedNetwork(isoNet *infrav1.CloudStackIsolatedNetwork) (
 		return nil
 	}
 
-	netDetails, count, err = c.cs.Network.GetNetworkByID(isoNet.Spec.ID)
+	netDetails, count, err = c.cs.Network.GetNetworkByID(isoNet.Spec.ID, cloudstack.WithProject(c.user.Project.ID))
 	if err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 
@@ -231,6 +232,7 @@ func (c *client) GetIsolatedNetwork(isoNet *infrav1.CloudStackIsolatedNetwork) (
 func (c *client) GetLoadBalancerRules(isoNet *infrav1.CloudStackIsolatedNetwork) ([]*cloudstack.LoadBalancerRule, error) {
 	p := c.cs.LoadBalancer.NewListLoadBalancerRulesParams()
 	p.SetPublicipid(isoNet.Status.APIServerLoadBalancer.IPAddressID)
+	setIfNotEmpty(c.user.Project.ID, p.SetProjectid)
 	loadBalancerRules, err := c.cs.LoadBalancer.ListLoadBalancerRules(p)
 	if err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
@@ -440,6 +442,7 @@ func (c *client) GetFirewallRules(isoNet *infrav1.CloudStackIsolatedNetwork) ([]
 	p := c.cs.Firewall.NewListFirewallRulesParams()
 	p.SetIpaddressid(isoNet.Status.APIServerLoadBalancer.IPAddressID)
 	p.SetNetworkid(isoNet.Spec.ID)
+	setIfNotEmpty(c.user.Project.ID, p.SetProjectid)
 	fwRules, err := c.cs.Firewall.ListFirewallRules(p)
 	if err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
@@ -883,7 +886,7 @@ func (c *client) DisassociatePublicIPAddressIfNotInUse(ipAddressID string) (bool
 	}
 	if tagsAllowDisposal, err := c.DoClusterTagsAllowDisposal(ResourceTypeIPAddress, ipAddressID); err != nil {
 		return false, err
-	} else if publicIP, _, err := c.cs.Address.GetPublicIpAddressByID(ipAddressID); err != nil {
+	} else if publicIP, _, err := c.cs.Address.GetPublicIpAddressByID(ipAddressID, cloudstack.WithProject(c.user.Project.ID)); err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 
 		return false, err
diff --git a/pkg/cloud/isolated_network_test.go b/pkg/cloud/isolated_network_test.go
index 7d089b64..0eeed2fd 100644
--- a/pkg/cloud/isolated_network_test.go
+++ b/pkg/cloud/isolated_network_test.go
@@ -75,8 +75,8 @@ var _ = Describe("Network", func() {
 			nos.EXPECT().GetNetworkOfferingID(gomock.Any()).Return("someOfferingID", 1, nil)
 			ns.EXPECT().NewCreateNetworkParams(gomock.Any(), gomock.Any(), gomock.Any()).
 				Return(&csapi.CreateNetworkParams{})
-			ns.EXPECT().GetNetworkByName(dummies.ISONet1.Name).Return(nil, 0, nil)
-			ns.EXPECT().GetNetworkByID(dummies.ISONet1.ID).Return(nil, 0, nil)
+			ns.EXPECT().GetNetworkByName(dummies.ISONet1.Name, gomock.Any()).Return(nil, 0, nil)
+			ns.EXPECT().GetNetworkByID(dummies.ISONet1.ID, gomock.Any()).Return(nil, 0, nil)
 			ns.EXPECT().CreateNetwork(gomock.Any()).Return(&csapi.CreateNetworkResponse{Id: dummies.ISONet1.ID}, nil)
 
 			fs.EXPECT().NewCreateEgressFirewallRuleParams(dummies.ISONet1.ID, gomock.Any()).
@@ -111,7 +111,7 @@ var _ = Describe("Network", func() {
 		It("resolves the existing isolated network", func() {
 			dummies.SetClusterSpecToNet(&dummies.ISONet1)
 
-			ns.EXPECT().GetNetworkByName(dummies.ISONet1.Name).Return(dummies.CAPCNetToCSAPINet(&dummies.ISONet1), 1, nil)
+			ns.EXPECT().GetNetworkByName(dummies.ISONet1.Name, gomock.Any()).Return(dummies.CAPCNetToCSAPINet(&dummies.ISONet1), 1, nil)
 
 			fs.EXPECT().NewCreateEgressFirewallRuleParams(dummies.ISONet1.ID, gomock.Any()).
 				DoAndReturn(func(_ string, protocol string) *csapi.CreateEgressFirewallRuleParams {
@@ -138,8 +138,8 @@ var _ = Describe("Network", func() {
 		})
 
 		It("fails to get network offering from CloudStack", func() {
-			ns.EXPECT().GetNetworkByName(dummies.ISONet1.Name).Return(nil, 0, nil)
-			ns.EXPECT().GetNetworkByID(dummies.ISONet1.ID).Return(nil, 0, nil)
+			ns.EXPECT().GetNetworkByName(dummies.ISONet1.Name, gomock.Any()).Return(nil, 0, nil)
+			ns.EXPECT().GetNetworkByID(dummies.ISONet1.ID, gomock.Any()).Return(nil, 0, nil)
 			nos.EXPECT().GetNetworkOfferingID(gomock.Any()).Return("", -1, fakeError)
 
 			err := client.GetOrCreateIsolatedNetwork(dummies.CSFailureDomain1, dummies.CSISONet1)
@@ -378,7 +378,7 @@ var _ = Describe("Network", func() {
 			})
 			fs.EXPECT().DeleteFirewallRule(gomock.Any()).Return(&csapi.DeleteFirewallRuleResponse{Success: true}, nil).Times(1)
 
-			as.EXPECT().GetPublicIpAddressByID(dummies.CSISONet1.Status.APIServerLoadBalancer.IPAddressID).Return(&csapi.PublicIpAddress{}, 1, nil)
+			as.EXPECT().GetPublicIpAddressByID(dummies.CSISONet1.Status.APIServerLoadBalancer.IPAddressID, gomock.Any()).Return(&csapi.PublicIpAddress{}, 1, nil)
 
 			rtdp := &csapi.DeleteTagsParams{}
 			rs.EXPECT().NewDeleteTagsParams(gomock.Any(), gomock.Any()).Return(rtdp)
@@ -1027,7 +1027,7 @@ var _ = Describe("Network", func() {
 			rtlp := &csapi.ListTagsParams{}
 			rs.EXPECT().NewListTagsParams().Return(rtlp).Times(4)
 			rs.EXPECT().ListTags(rtlp).Return(&csapi.ListTagsResponse{}, nil).Times(4)
-			as.EXPECT().GetPublicIpAddressByID(dummies.CSISONet1.Status.PublicIPID).Return(&csapi.PublicIpAddress{}, 1, nil)
+			as.EXPECT().GetPublicIpAddressByID(dummies.CSISONet1.Status.PublicIPID, gomock.Any()).Return(&csapi.PublicIpAddress{}, 1, nil)
 
 			Ω(client.DisposeIsoNetResources(dummies.CSISONet1, dummies.CSCluster)).Should(Succeed())
 		})
@@ -1043,7 +1043,7 @@ var _ = Describe("Network", func() {
 			rs.EXPECT().NewListTagsParams().Return(rtlp).Times(4)
 			rs.EXPECT().ListTags(rtlp).Return(createdByCAPCResponse, nil).Times(3)
 			rs.EXPECT().ListTags(rtlp).Return(&csapi.ListTagsResponse{}, nil).Times(1)
-			as.EXPECT().GetPublicIpAddressByID(dummies.CSISONet1.Status.PublicIPID).Return(&csapi.PublicIpAddress{}, 1, nil)
+			as.EXPECT().GetPublicIpAddressByID(dummies.CSISONet1.Status.PublicIPID, gomock.Any()).Return(&csapi.PublicIpAddress{}, 1, nil)
 			as.EXPECT().NewDisassociateIpAddressParams(dummies.CSISONet1.Status.PublicIPID).Return(dap)
 			as.EXPECT().DisassociateIpAddress(dap).Return(&csapi.DisassociateIpAddressResponse{}, nil)
 
@@ -1060,7 +1060,7 @@ var _ = Describe("Network", func() {
 			rs.EXPECT().DeleteTags(rtdp).Return(&csapi.DeleteTagsResponse{}, nil).Times(2)
 			rs.EXPECT().NewListTagsParams().Return(rtlp).Times(2)
 			rs.EXPECT().ListTags(rtlp).Return(createdByCAPCResponse, nil).Times(2)
-			as.EXPECT().GetPublicIpAddressByID(dummies.CSISONet1.Status.PublicIPID).Return(&csapi.PublicIpAddress{}, 1, nil)
+			as.EXPECT().GetPublicIpAddressByID(dummies.CSISONet1.Status.PublicIPID, gomock.Any()).Return(&csapi.PublicIpAddress{}, 1, nil)
 			as.EXPECT().NewDisassociateIpAddressParams(dummies.CSISONet1.Status.PublicIPID).Return(dap)
 			as.EXPECT().DisassociateIpAddress(dap).Return(nil, fakeError)
 
diff --git a/pkg/cloud/network.go b/pkg/cloud/network.go
index dc09258c..de31f121 100644
--- a/pkg/cloud/network.go
+++ b/pkg/cloud/network.go
@@ -17,6 +17,7 @@ limitations under the License.
 package cloud
 
 import (
+	"github.com/apache/cloudstack-go/v2/cloudstack"
 	"github.com/hashicorp/go-multierror"
 	"github.com/pkg/errors"
 
@@ -42,7 +43,7 @@ func (c *client) ResolveNetwork(net *infrav1.Network) (retErr error) {
 	// TODO rebuild this to consider cases with networks in many zones.
 	// Use ListNetworks instead.
 	netName := net.Name
-	netDetails, count, err := c.cs.Network.GetNetworkByName(netName)
+	netDetails, count, err := c.cs.Network.GetNetworkByName(netName, cloudstack.WithProject(c.user.Project.ID))
 	if err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 		retErr = multierror.Append(retErr, errors.Wrapf(err, "could not get Network ID from %s", netName))
@@ -59,7 +60,7 @@ func (c *client) ResolveNetwork(net *infrav1.Network) (retErr error) {
 	}
 
 	// Now get network details.
-	netDetails, count, err = c.cs.Network.GetNetworkByID(net.ID)
+	netDetails, count, err = c.cs.Network.GetNetworkByID(net.ID, cloudstack.WithProject(c.user.Project.ID))
 	if err != nil {
 		return multierror.Append(retErr, errors.Wrapf(err, "could not get Network by ID %s", net.ID))
 	} else if count != 1 {
diff --git a/pkg/cloud/network_test.go b/pkg/cloud/network_test.go
index 79f04610..9135dfdd 100644
--- a/pkg/cloud/network_test.go
+++ b/pkg/cloud/network_test.go
@@ -54,21 +54,21 @@ var _ = Describe("Network", func() {
 
 	Context("for an existing network", func() {
 		It("resolves network by ID", func() {
-			ns.EXPECT().GetNetworkByName(dummies.ISONet1.Name).Return(nil, 0, nil)
-			ns.EXPECT().GetNetworkByID(dummies.ISONet1.ID).Return(dummies.CAPCNetToCSAPINet(&dummies.ISONet1), 1, nil)
+			ns.EXPECT().GetNetworkByName(dummies.ISONet1.Name, gomock.Any()).Return(nil, 0, nil)
+			ns.EXPECT().GetNetworkByID(dummies.ISONet1.ID, gomock.Any()).Return(dummies.CAPCNetToCSAPINet(&dummies.ISONet1), 1, nil)
 
 			Ω(client.ResolveNetwork(&dummies.ISONet1)).Should(Succeed())
 		})
 
 		It("resolves network by Name", func() {
-			ns.EXPECT().GetNetworkByName(dummies.ISONet1.Name).Return(dummies.CAPCNetToCSAPINet(&dummies.ISONet1), 1, nil)
+			ns.EXPECT().GetNetworkByName(dummies.ISONet1.Name, gomock.Any()).Return(dummies.CAPCNetToCSAPINet(&dummies.ISONet1), 1, nil)
 
 			Ω(client.ResolveNetwork(&dummies.ISONet1)).Should(Succeed())
 		})
 
 		It("When there exists more than one network with the same name", func() {
-			ns.EXPECT().GetNetworkByName(dummies.ISONet1.Name).Return(dummies.CAPCNetToCSAPINet(&dummies.ISONet1), 2, nil)
-			ns.EXPECT().GetNetworkByID(dummies.ISONet1.ID).Return(nil, 2, errors.New("There is more then one result for Network UUID"))
+			ns.EXPECT().GetNetworkByName(dummies.ISONet1.Name, gomock.Any()).Return(dummies.CAPCNetToCSAPINet(&dummies.ISONet1), 2, nil)
+			ns.EXPECT().GetNetworkByID(dummies.ISONet1.ID, gomock.Any()).Return(nil, 2, errors.New("There is more then one result for Network UUID"))
 			err := client.ResolveNetwork(&dummies.ISONet1)
 			Ω(err).ShouldNot(Succeed())
 			Ω(err.Error()).Should(ContainSubstring(fmt.Sprintf("expected 1 Network with name %s, but got %d", dummies.ISONet1.Name, 2)))
diff --git a/pkg/cloud/tags.go b/pkg/cloud/tags.go
index c569a882..0515c523 100644
--- a/pkg/cloud/tags.go
+++ b/pkg/cloud/tags.go
@@ -139,6 +139,7 @@ func (c *client) GetTags(rType ResourceType, rID string) (map[string]string, err
 	p.SetResourceid(rID)
 	p.SetResourcetype(string(rType))
 	p.SetListall(true)
+	setIfNotEmpty(c.user.Project.ID, p.SetProjectid)
 	listTagResponse, err := c.cs.Resourcetags.ListTags(p)
 	if err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
diff --git a/pkg/cloud/user_credentials.go b/pkg/cloud/user_credentials.go
index 9acd2818..8c5d64c5 100644
--- a/pkg/cloud/user_credentials.go
+++ b/pkg/cloud/user_credentials.go
@@ -55,6 +55,15 @@ type Account struct {
 	VMAvailable     string
 }
 
+// Project contains specifications that identify a project.
+type Project struct {
+	Name            string
+	ID              string
+	CPUAvailable    string
+	MemoryAvailable string
+	VMAvailable     string
+}
+
 // User contains information uniquely identifying and scoping a user.
 type User struct {
 	ID        string
@@ -62,6 +71,7 @@ type User struct {
 	APIKey    string
 	SecretKey string
 	Account
+	Project
 }
 
 // ResolveDomain resolves a domain's information.
@@ -174,6 +184,37 @@ func (c *client) ResolveAccount(account *Account) error {
 	return nil
 }
 
+// ResolveProject resolves a project's information.
+func (c *client) ResolveProject(user *User) error {
+	if user.Project.ID == "" {
+		return nil
+	}
+
+	p := c.cs.Project.NewListProjectsParams()
+	p.SetListall(true)
+	p.SetDomainid(c.user.Domain.ID)
+	p.SetAccount(c.user.Account.Name)
+	p.SetId(user.Project.ID)
+	resp, err := c.cs.Project.ListProjects(p)
+	if err != nil {
+		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
+
+		return err
+	} else if resp.Count == 0 {
+		return errors.Errorf("could not find project with ID '%s'", user.Project.ID)
+	} else if resp.Count != 1 {
+		return errors.Errorf("expected 1 Project with ID '%s' in domain ID '%s', but got %d",
+			user.Project.ID, c.user.Domain.ID, resp.Count)
+	}
+	c.user.Project.ID = resp.Projects[0].Id
+	c.user.Project.Name = resp.Projects[0].Name
+	c.user.Project.CPUAvailable = resp.Projects[0].Cpuavailable
+	c.user.Project.MemoryAvailable = resp.Projects[0].Memoryavailable
+	c.user.Project.VMAvailable = resp.Projects[0].Vmavailable
+
+	return nil
+}
+
 // ResolveUser resolves a user's information.
 func (c *client) ResolveUser(user *User) error {
 	// Resolve account prior to any user resolution activity.
@@ -229,6 +270,10 @@ func (c *client) GetUserWithKeys(user *User) (bool, error) {
 		return false, errors.Wrapf(err, "resolving account %s details", user.Account.Name)
 	}
 
+	if err := c.ResolveProject(user); err != nil {
+		return false, errors.Wrapf(err, "resolving project %s details", user.Project.Name)
+	}
+
 	// List users and take first user that has already has api keys.
 	p := c.cs.User.NewListUsersParams()
 	p.SetAccount(user.Account.Name)
diff --git a/pkg/cloud/zone.go b/pkg/cloud/zone.go
index 9147e207..f6f2cb5a 100644
--- a/pkg/cloud/zone.go
+++ b/pkg/cloud/zone.go
@@ -17,6 +17,7 @@ limitations under the License.
 package cloud
 
 import (
+	"github.com/apache/cloudstack-go/v2/cloudstack"
 	"github.com/hashicorp/go-multierror"
 	"github.com/pkg/errors"
 
@@ -57,7 +58,7 @@ func (c *client) ResolveZone(zSpec *infrav1.CloudStackZoneSpec) (retErr error) {
 // ResolveNetworkForZone fetches details on Zone's specified network.
 func (c *client) ResolveNetworkForZone(zSpec *infrav1.CloudStackZoneSpec) (retErr error) {
 	netName := zSpec.Network.Name
-	netDetails, count, err := c.cs.Network.GetNetworkByName(netName)
+	netDetails, count, err := c.cs.Network.GetNetworkByName(netName, cloudstack.WithProject(c.user.Project.ID))
 	if err != nil {
 		c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
 		retErr = multierror.Append(retErr, errors.Wrapf(err, "could not get Network ID from %v", netName))
@@ -72,7 +73,7 @@ func (c *client) ResolveNetworkForZone(zSpec *infrav1.CloudStackZoneSpec) (retEr
 	}
 
 	// Now get network details.
-	netDetails, count, err = c.cs.Network.GetNetworkByID(zSpec.Network.ID)
+	netDetails, count, err = c.cs.Network.GetNetworkByID(zSpec.Network.ID, cloudstack.WithProject(c.user.Project.ID))
 	if err != nil {
 		return multierror.Append(retErr, errors.Wrapf(err, "could not get Network by ID %s", zSpec.Network.ID))
 	} else if count != 1 {
diff --git a/pkg/cloud/zone_test.go b/pkg/cloud/zone_test.go
index 4f91fd2f..2397e7f5 100644
--- a/pkg/cloud/zone_test.go
+++ b/pkg/cloud/zone_test.go
@@ -85,30 +85,30 @@ var _ = Describe("Zone", func() {
 	})
 
 	Context("Resolve network for zone", func() {
-		It("get network by name specfied in zone spec", func() {
-			ns.EXPECT().GetNetworkByName(dummies.Zone1.Network.Name).Return(&csapi.Network{}, 1, nil)
+		It("get network by name specified in zone spec", func() {
+			ns.EXPECT().GetNetworkByName(dummies.Zone1.Network.Name, gomock.Any()).Return(&csapi.Network{}, 1, nil)
 
 			Ω(client.ResolveNetworkForZone(&dummies.CSFailureDomain1.Spec.Zone)).Should(Succeed())
 		})
 
-		It("get network by name specfied in zone spec returns > 1 network", func() {
-			ns.EXPECT().GetNetworkByName(dummies.Zone2.Network.Name).Return(&csapi.Network{}, 2, nil)
-			ns.EXPECT().GetNetworkByID(dummies.Zone2.Network.ID).Return(&csapi.Network{}, 2, nil)
+		It("get network by name specified in zone spec returns > 1 network", func() {
+			ns.EXPECT().GetNetworkByName(dummies.Zone2.Network.Name, gomock.Any()).Return(&csapi.Network{}, 2, nil)
+			ns.EXPECT().GetNetworkByID(dummies.Zone2.Network.ID, gomock.Any()).Return(&csapi.Network{}, 2, nil)
 
 			Ω(client.ResolveNetworkForZone(&dummies.CSFailureDomain2.Spec.Zone)).Should(MatchError(And(
 				ContainSubstring(fmt.Sprintf("expected 1 Network with name %s, but got %d", dummies.Zone2.Network.Name, 2)),
 				ContainSubstring(fmt.Sprintf("expected 1 Network with UUID %v, but got %d", dummies.Zone2.Network.ID, 2)))))
 		})
 
-		It("get network by id specfied in zone spec", func() {
-			ns.EXPECT().GetNetworkByName(dummies.Zone2.Network.Name).Return(nil, -1, fakeError)
-			ns.EXPECT().GetNetworkByID(dummies.Zone2.Network.ID).Return(&csapi.Network{}, 1, nil)
+		It("get network by id specified in zone spec", func() {
+			ns.EXPECT().GetNetworkByName(dummies.Zone2.Network.Name, gomock.Any()).Return(nil, -1, fakeError)
+			ns.EXPECT().GetNetworkByID(dummies.Zone2.Network.ID, gomock.Any()).Return(&csapi.Network{}, 1, nil)
 			Ω(client.ResolveNetworkForZone(&dummies.CSFailureDomain2.Spec.Zone)).Should(Succeed())
 		})
 
 		It("get network by id fails", func() {
-			ns.EXPECT().GetNetworkByName(dummies.Zone2.Network.Name).Return(nil, -1, fakeError)
-			ns.EXPECT().GetNetworkByID(dummies.Zone2.Network.ID).Return(nil, -1, fakeError)
+			ns.EXPECT().GetNetworkByName(dummies.Zone2.Network.Name, gomock.Any()).Return(nil, -1, fakeError)
+			ns.EXPECT().GetNetworkByID(dummies.Zone2.Network.ID, gomock.Any()).Return(nil, -1, fakeError)
 
 			Ω(client.ResolveNetworkForZone(&dummies.CSFailureDomain2.Spec.Zone).Error()).Should(ContainSubstring("could not get Network by ID " + dummies.Zone2.Network.ID))
 		})
diff --git a/test/e2e/common.go b/test/e2e/common.go
index 6586827a..4e4500ef 100644
--- a/test/e2e/common.go
+++ b/test/e2e/common.go
@@ -280,12 +280,16 @@ func DestroyOneMachine(client *cloudstack.CloudStackClient, clusterName string,
 }
 
 func CheckAffinityGroupsDeleted(client *cloudstack.CloudStackClient, affinityIds []string) error {
+	return CheckAffinityGroupsDeletedInProject(client, affinityIds, "")
+}
+
+func CheckAffinityGroupsDeletedInProject(client *cloudstack.CloudStackClient, affinityIds []string, project string) error {
 	if len(affinityIds) == 0 {
 		return errors.New("affinityIds are empty")
 	}
 
 	for _, affinityId := range affinityIds {
-		affinity, count, _ := client.AffinityGroup.GetAffinityGroupByID(affinityId)
+		affinity, count, _ := client.AffinityGroup.GetAffinityGroupByID(affinityId, cloudstack.WithProject(project))
 		if count > 0 {
 			return errors.New("Affinity group " + affinity.Name + " still exists")
 		}
@@ -312,9 +316,21 @@ func GetHostCount(client *cloudstack.CloudStackClient, zoneName string) int {
 }
 
 func CheckAffinityGroup(client *cloudstack.CloudStackClient, clusterName string, affinityType string) []string {
+	return CheckAffinityGroupInProject(client, clusterName, affinityType, "")
+}
+
+func CheckAffinityGroupInProject(client *cloudstack.CloudStackClient, clusterName string, affinityType string, project string) []string {
 	By("Listing all machines")
 	p := client.VirtualMachine.NewListVirtualMachinesParams()
 	p.SetListall(true)
+
+	if project != "" {
+		projectID, _, err := client.Project.GetProjectID(project)
+		if err != nil {
+			Fail("Failed to get project: " + err.Error())
+		}
+		p.SetProjectid(projectID)
+	}
 	listResp, err := client.VirtualMachine.ListVirtualMachines(p)
 	if err != nil {
 		Fail("Failed to list machines: " + err.Error())
@@ -334,7 +350,7 @@ func CheckAffinityGroup(client *cloudstack.CloudStackClient, clusterName string,
 
 			for _, affinity := range vm.Affinitygroup {
 				affinityIds = append(affinityIds, affinity.Id)
-				affinity, _, _ := client.AffinityGroup.GetAffinityGroupByID(affinity.Id)
+				affinity, _, _ := client.AffinityGroup.GetAffinityGroupByID(affinity.Id, cloudstack.WithProject(project))
 				if err != nil {
 					Fail("Failed to get affinity group for " + affinity.Id + " : " + err.Error())
 				}
diff --git a/test/e2e/config/cloudstack.yaml b/test/e2e/config/cloudstack.yaml
index 2d06d996..79054d55 100644
--- a/test/e2e/config/cloudstack.yaml
+++ b/test/e2e/config/cloudstack.yaml
@@ -94,6 +94,7 @@ providers:
       - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-disk-offering.yaml"
       - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-custom-disk-offering.yaml"
       - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-subdomain.yaml"
+      - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-project.yaml"
       - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-invalid-ip.yaml"
       - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-kubernetes-version-upgrade-before.yaml"
       - sourcePath: "../data/infrastructure-cloudstack/v1beta3/cluster-template-kubernetes-version-upgrade-after.yaml"
@@ -122,6 +123,7 @@ variables:
   CLOUDSTACK_INVALID_ZONE_NAME: zoneXXXX
   CLOUDSTACK_INVALID_NETWORK_NAME: networkXXXX
   CLOUDSTACK_ACCOUNT_NAME: admin
+  CLOUDSTACK_PROJECT_NAME: capc-e2e-test
   CLOUDSTACK_INVALID_ACCOUNT_NAME: accountXXXX
   CLOUDSTACK_DOMAIN_NAME: ROOT
   CLOUDSTACK_INVALID_DOMAIN_NAME: domainXXXX
diff --git a/test/fakes/fakes.infrastructure.cluster.x-k8s.io_cloudstackmachineowners.yaml b/test/fakes/fakes.infrastructure.cluster.x-k8s.io_cloudstackmachineowners.yaml
index f8a7c46e..8d85b4f5 100644
--- a/test/fakes/fakes.infrastructure.cluster.x-k8s.io_cloudstackmachineowners.yaml
+++ b/test/fakes/fakes.infrastructure.cluster.x-k8s.io_cloudstackmachineowners.yaml
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.14.0
+    controller-gen.kubebuilder.io/version: v0.15.0
   name: cloudstackmachineowners.fakes.infrastructure.cluster.x-k8s.io
 spec:
   group: fakes.infrastructure.cluster.x-k8s.io