Skip to content

Commit

Permalink
Remove VsphereFailureDomain from context
Browse files Browse the repository at this point in the history
Signed-off-by: killianmuldoon <[email protected]>
  • Loading branch information
killianmuldoon committed Sep 27, 2023
1 parent 882a0e0 commit 71c9d67
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 93 deletions.
25 changes: 11 additions & 14 deletions controllers/vspheredeploymentzone_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,7 @@ func (r vsphereDeploymentZoneReconciler) reconcileNormal(ctx context.Context, de
if err := r.Client.Get(ctx, failureDomainKey, failureDomain); err != nil {
return errors.Wrap(err, "VSphereFailureDomain could not be retrieved")
}
deploymentZoneCtx.VSphereFailureDomain = failureDomain
authSession, err := r.getVCenterSession(ctx, deploymentZoneCtx)
authSession, err := r.getVCenterSession(ctx, deploymentZoneCtx, failureDomain.Spec.Topology.Datacenter)
if err != nil {
deploymentZoneCtx.Logger.V(4).Error(err, "unable to create session")
conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VCenterAvailableCondition, infrav1.VCenterUnreachableReason, clusterv1.ConditionSeverityError, err.Error())
Expand All @@ -173,7 +172,7 @@ func (r vsphereDeploymentZoneReconciler) reconcileNormal(ctx context.Context, de
conditions.MarkTrue(deploymentZoneCtx.VSphereDeploymentZone, infrav1.PlacementConstraintMetCondition)

// reconcile the failure domain
if err := r.reconcileFailureDomain(ctx, deploymentZoneCtx); err != nil {
if err := r.reconcileFailureDomain(ctx, deploymentZoneCtx, failureDomain); err != nil {
deploymentZoneCtx.Logger.V(4).Error(err, "failed to reconcile failure domain", "failureDomain", deploymentZoneCtx.VSphereDeploymentZone.Spec.FailureDomain)
deploymentZoneCtx.VSphereDeploymentZone.Status.Ready = pointer.Bool(false)
return errors.Wrapf(err, "failed to reconcile failure domain")
Expand Down Expand Up @@ -205,10 +204,10 @@ func (r vsphereDeploymentZoneReconciler) reconcilePlacementConstraint(ctx contex
return nil
}

func (r vsphereDeploymentZoneReconciler) getVCenterSession(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext) (*session.Session, error) {
func (r vsphereDeploymentZoneReconciler) getVCenterSession(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext, datacenter string) (*session.Session, error) {
params := session.NewParams().
WithServer(deploymentZoneCtx.VSphereDeploymentZone.Spec.Server).
WithDatacenter(deploymentZoneCtx.VSphereFailureDomain.Spec.Topology.Datacenter).
WithDatacenter(datacenter).
WithUserInfo(r.ControllerContext.Username, r.ControllerContext.Password).
WithFeatures(session.Feature{
EnableKeepAlive: r.EnableKeepAlive,
Expand Down Expand Up @@ -259,8 +258,7 @@ func (r vsphereDeploymentZoneReconciler) reconcileDelete(ctx context.Context, de
})
if len(machinesUsingDeploymentZone) > 0 {
machineNamesStr := util.MachinesAsString(machinesUsingDeploymentZone.SortedByCreationTimestamp())
err := errors.Errorf("error deleting VSphereDeploymentZone: %s is currently in use by machines: %s", deploymentZoneCtx.VSphereDeploymentZone.Name, machineNamesStr)
return err
return errors.Errorf("error deleting VSphereDeploymentZone: %s is currently in use by machines: %s", deploymentZoneCtx.VSphereDeploymentZone.Name, machineNamesStr)
}

failureDomain := &infrav1.VSphereFailureDomain{}
Expand All @@ -275,11 +273,10 @@ func (r vsphereDeploymentZoneReconciler) reconcileDelete(ctx context.Context, de
}
return err
}
deploymentZoneCtx.VSphereFailureDomain = failureDomain

// Reconcile the deletion of the VSphereFailureDomain by removing ownerReferences and deleting if necessary.
if err := updateOwnerReferences(deploymentZoneCtx, deploymentZoneCtx.VSphereFailureDomain, r.Client, func() []metav1.OwnerReference {
return clusterutilv1.RemoveOwnerRef(deploymentZoneCtx.VSphereFailureDomain.OwnerReferences, metav1.OwnerReference{
if err := updateOwnerReferences(ctx, failureDomain, r.Client, func() []metav1.OwnerReference {
return clusterutilv1.RemoveOwnerRef(failureDomain.OwnerReferences, metav1.OwnerReference{
APIVersion: infrav1.GroupVersion.String(),
Kind: deploymentZoneCtx.VSphereDeploymentZone.Kind,
Name: deploymentZoneCtx.VSphereDeploymentZone.Name,
Expand All @@ -288,10 +285,10 @@ func (r vsphereDeploymentZoneReconciler) reconcileDelete(ctx context.Context, de
return err
}

if len(deploymentZoneCtx.VSphereFailureDomain.OwnerReferences) == 0 {
deploymentZoneCtx.Logger.Info("deleting VSphereFailureDomain", "name", deploymentZoneCtx.VSphereFailureDomain.Name)
if err := r.Client.Delete(ctx, deploymentZoneCtx.VSphereFailureDomain); err != nil && !apierrors.IsNotFound(err) {
return errors.Wrapf(err, "failed to delete VSphereFailureDomain %s", deploymentZoneCtx.VSphereFailureDomain.Name)
if len(failureDomain.OwnerReferences) == 0 {
deploymentZoneCtx.Logger.Info("deleting VSphereFailureDomain", "name", failureDomain.Name)
if err := r.Client.Delete(ctx, failureDomain); err != nil && !apierrors.IsNotFound(err) {
return errors.Wrapf(err, "failed to delete VSphereFailureDomain %s", failureDomain.Name)
}
}

Expand Down
40 changes: 20 additions & 20 deletions controllers/vspheredeploymentzone_controller_domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,40 +35,40 @@ import (
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/taggable"
)

func (r vsphereDeploymentZoneReconciler) reconcileFailureDomain(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext) error {
logger := ctrl.LoggerFrom(ctx).WithValues("VSphereFailureDomain", klog.KObj(deploymentZoneCtx.VSphereFailureDomain))
func (r vsphereDeploymentZoneReconciler) reconcileFailureDomain(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext, vsphereFailureDomain *infrav1.VSphereFailureDomain) error {
logger := ctrl.LoggerFrom(ctx).WithValues("VSphereFailureDomain", klog.KObj(vsphereFailureDomain))

// verify the failure domain for the region
if err := r.reconcileInfraFailureDomain(ctx, deploymentZoneCtx, deploymentZoneCtx.VSphereFailureDomain.Spec.Region); err != nil {
if err := r.reconcileInfraFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain, vsphereFailureDomain.Spec.Region); err != nil {
conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.RegionMisconfiguredReason, clusterv1.ConditionSeverityError, err.Error())
logger.Error(err, "region is not configured correctly")
return errors.Wrapf(err, "region is not configured correctly")
}

// verify the failure domain for the zone
if err := r.reconcileInfraFailureDomain(ctx, deploymentZoneCtx, deploymentZoneCtx.VSphereFailureDomain.Spec.Zone); err != nil {
if err := r.reconcileInfraFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain, vsphereFailureDomain.Spec.Zone); err != nil {
conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.ZoneMisconfiguredReason, clusterv1.ConditionSeverityError, err.Error())
logger.Error(err, "zone is not configured correctly")
return errors.Wrapf(err, "zone is not configured correctly")
}

if computeCluster := deploymentZoneCtx.VSphereFailureDomain.Spec.Topology.ComputeCluster; computeCluster != nil {
if err := r.reconcileComputeCluster(ctx, deploymentZoneCtx); err != nil {
if computeCluster := vsphereFailureDomain.Spec.Topology.ComputeCluster; computeCluster != nil {
if err := r.reconcileComputeCluster(ctx, deploymentZoneCtx, vsphereFailureDomain); err != nil {
logger.Error(err, "compute cluster is not configured correctly", "name", *computeCluster)
return errors.Wrap(err, "compute cluster is not configured correctly")
}
}

if err := r.reconcileTopology(ctx, deploymentZoneCtx); err != nil {
if err := r.reconcileTopology(ctx, deploymentZoneCtx, vsphereFailureDomain); err != nil {
logger.Error(err, "topology is not configured correctly")
return errors.Wrap(err, "topology is not configured correctly")
}

// Ensure the VSphereDeploymentZone is marked as an owner of the VSphereFailureDomain.
if err := updateOwnerReferences(ctx, deploymentZoneCtx.VSphereFailureDomain, r.Client,
if err := updateOwnerReferences(ctx, vsphereFailureDomain, r.Client,
func() []metav1.OwnerReference {
return clusterutilv1.EnsureOwnerRef(
deploymentZoneCtx.VSphereFailureDomain.OwnerReferences,
vsphereFailureDomain.OwnerReferences,
metav1.OwnerReference{
APIVersion: infrav1.GroupVersion.String(),
Kind: deploymentZoneCtx.VSphereDeploymentZone.Kind,
Expand All @@ -84,15 +84,15 @@ func (r vsphereDeploymentZoneReconciler) reconcileFailureDomain(ctx context.Cont
return nil
}

func (r vsphereDeploymentZoneReconciler) reconcileInfraFailureDomain(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext, failureDomain infrav1.FailureDomain) error {
func (r vsphereDeploymentZoneReconciler) reconcileInfraFailureDomain(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext, vsphereFailureDomain *infrav1.VSphereFailureDomain, failureDomain infrav1.FailureDomain) error {
if *failureDomain.AutoConfigure { //nolint:staticcheck
return r.createAndAttachMetadata(ctx, deploymentZoneCtx, failureDomain)
return r.createAndAttachMetadata(ctx, deploymentZoneCtx, vsphereFailureDomain, failureDomain)
}
return r.verifyFailureDomain(ctx, deploymentZoneCtx, failureDomain)
return r.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain, failureDomain)
}

func (r vsphereDeploymentZoneReconciler) reconcileTopology(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext) error {
topology := deploymentZoneCtx.VSphereFailureDomain.Spec.Topology
func (r vsphereDeploymentZoneReconciler) reconcileTopology(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext, vsphereFailureDomain *infrav1.VSphereFailureDomain) error {
topology := vsphereFailureDomain.Spec.Topology
if datastore := topology.Datastore; datastore != "" {
if _, err := deploymentZoneCtx.AuthSession.Finder.Datastore(ctx, datastore); err != nil {
conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.DatastoreNotFoundReason, clusterv1.ConditionSeverityError, "datastore %s is misconfigured", datastore)
Expand Down Expand Up @@ -123,8 +123,8 @@ func (r vsphereDeploymentZoneReconciler) reconcileTopology(ctx context.Context,
return nil
}

func (r vsphereDeploymentZoneReconciler) reconcileComputeCluster(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext) error {
computeCluster := deploymentZoneCtx.VSphereFailureDomain.Spec.Topology.ComputeCluster
func (r vsphereDeploymentZoneReconciler) reconcileComputeCluster(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext, vsphereFailureDomain *infrav1.VSphereFailureDomain) error {
computeCluster := vsphereFailureDomain.Spec.Topology.ComputeCluster
if computeCluster == nil {
return nil
}
Expand Down Expand Up @@ -156,12 +156,12 @@ func (r vsphereDeploymentZoneReconciler) reconcileComputeCluster(ctx context.Con

// verifyFailureDomain verifies the Failure Domain. It verifies the existence of tag and category specified and
// checks whether the specified tags exist on the DataCenter or Compute Cluster or Hosts (in a HostGroup).
func (r vsphereDeploymentZoneReconciler) verifyFailureDomain(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext, failureDomain infrav1.FailureDomain) error {
func (r vsphereDeploymentZoneReconciler) verifyFailureDomain(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext, vsphereFailureDomain *infrav1.VSphereFailureDomain, failureDomain infrav1.FailureDomain) error {
if _, err := deploymentZoneCtx.AuthSession.TagManager.GetTagForCategory(ctx, failureDomain.Name, failureDomain.TagCategory); err != nil {
return errors.Wrapf(err, "failed to verify tag %s and category %s", failureDomain.Name, failureDomain.TagCategory)
}

objects, err := taggable.GetObjects(ctx, deploymentZoneCtx, failureDomain.Type)
objects, err := taggable.GetObjects(ctx, deploymentZoneCtx, vsphereFailureDomain, failureDomain.Type)
if err != nil {
return errors.Wrapf(err, "failed to find object")
}
Expand All @@ -179,7 +179,7 @@ func (r vsphereDeploymentZoneReconciler) verifyFailureDomain(ctx context.Context
return nil
}

func (r vsphereDeploymentZoneReconciler) createAndAttachMetadata(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext, failureDomain infrav1.FailureDomain) error {
func (r vsphereDeploymentZoneReconciler) createAndAttachMetadata(ctx context.Context, deploymentZoneCtx *capvcontext.VSphereDeploymentZoneContext, vsphereFailureDomain *infrav1.VSphereFailureDomain, failureDomain infrav1.FailureDomain) error {
logger := ctrl.LoggerFrom(ctx, "tag", failureDomain.Name, "category", failureDomain.TagCategory)
categoryID, err := metadata.CreateCategory(ctx, deploymentZoneCtx, failureDomain.TagCategory, failureDomain.Type)
if err != nil {
Expand All @@ -193,7 +193,7 @@ func (r vsphereDeploymentZoneReconciler) createAndAttachMetadata(ctx context.Con
}

logger = logger.WithValues("type", failureDomain.Type)
objects, err := taggable.GetObjects(ctx, deploymentZoneCtx, failureDomain.Type)
objects, err := taggable.GetObjects(ctx, deploymentZoneCtx, vsphereFailureDomain, failureDomain.Type)
if err != nil {
logger.V(4).Error(err, "failed to find object")
return err
Expand Down
57 changes: 26 additions & 31 deletions controllers/vspheredeploymentzone_controller_domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,31 +87,30 @@ func TestVsphereDeploymentZoneReconciler_Reconcile_VerifyFailureDomain_ComputeCl
}

deploymentZoneCtx := &capvcontext.VSphereDeploymentZoneContext{
ControllerContext: controllerCtx,
VSphereFailureDomain: vsphereFailureDomain,
Logger: logr.Discard(),
AuthSession: authSession,
ControllerContext: controllerCtx,
Logger: logr.Discard(),
AuthSession: authSession,
}

reconciler := vsphereDeploymentZoneReconciler{controllerCtx}

g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain.Spec.Region)).To(Succeed())
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain, vsphereFailureDomain.Spec.Region)).To(Succeed())
stdout := gbytes.NewBuffer()
g.Expect(simr.Run("tags.attached.ls k8s-region-west", stdout)).To(Succeed())
g.Expect(stdout).Should(gbytes.Say("Datacenter"))

g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain.Spec.Zone)).To(Succeed())
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain, vsphereFailureDomain.Spec.Zone)).To(Succeed())
stdout = gbytes.NewBuffer()
g.Expect(simr.Run("tags.attached.ls k8s-region-west-2", stdout)).To(Succeed())
g.Expect(stdout).Should(gbytes.Say("ClusterComputeResource"))

vsphereFailureDomain.Spec.Topology.ComputeCluster = pointer.String("DC0_C1")
// Since association is verified, the method errors since the tag is not associated to the object.
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain.Spec.Zone)).To(HaveOccurred())
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain, vsphereFailureDomain.Spec.Zone)).To(HaveOccurred())

// Since the tag does not belong to the category
vsphereFailureDomain.Spec.Zone.TagCategory = "diff-k8s-region"
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain.Spec.Zone)).To(HaveOccurred())
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain, vsphereFailureDomain.Spec.Zone)).To(HaveOccurred())
}

func TestVsphereDeploymentZoneReconciler_Reconcile_VerifyFailureDomain_HostGroupZone(t *testing.T) {
Expand Down Expand Up @@ -171,29 +170,28 @@ func TestVsphereDeploymentZoneReconciler_Reconcile_VerifyFailureDomain_HostGroup
}

deploymentZoneCtx := &capvcontext.VSphereDeploymentZoneContext{
ControllerContext: controllerCtx,
VSphereFailureDomain: vsphereFailureDomain,
Logger: logr.Discard(),
AuthSession: authSession,
ControllerContext: controllerCtx,
Logger: logr.Discard(),
AuthSession: authSession,
}

reconciler := vsphereDeploymentZoneReconciler{controllerCtx}

// Fails since no hosts are tagged
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain.Spec.Zone)).To(HaveOccurred())
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain, vsphereFailureDomain.Spec.Zone)).To(HaveOccurred())
stdout := gbytes.NewBuffer()

g.Expect(simr.Run("tags.attach k8s-region-west-2 /DC0/host/DC0_C0/DC0_C0_H0", stdout)).To(Succeed())
// Fails as not all hosts are tagged
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain.Spec.Zone)).To(HaveOccurred())
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain, vsphereFailureDomain.Spec.Zone)).To(HaveOccurred())

g.Expect(simr.Run("tags.attach k8s-region-west-2 /DC0/host/DC0_C0/DC0_C0_H1", stdout)).To(Succeed())
// Succeeds as all hosts are tagged
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain.Spec.Zone)).To(Succeed())
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain, vsphereFailureDomain.Spec.Zone)).To(Succeed())

// Since the tag does not belong to the category
vsphereFailureDomain.Spec.Zone.TagCategory = "diff-k8s-region"
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain.Spec.Zone)).To(HaveOccurred())
g.Expect(reconciler.verifyFailureDomain(ctx, deploymentZoneCtx, vsphereFailureDomain, vsphereFailureDomain.Spec.Zone)).To(HaveOccurred())
}

func TestVsphereDeploymentZoneReconciler_Reconcile_CreateAndAttachMetadata(t *testing.T) {
Expand Down Expand Up @@ -282,13 +280,12 @@ func TestVsphereDeploymentZoneReconciler_Reconcile_CreateAndAttachMetadata(t *te
}

deploymentZoneCtx := &capvcontext.VSphereDeploymentZoneContext{
ControllerContext: controllerCtx,
VSphereFailureDomain: vsphereFailureDomain,
Logger: logr.Discard(),
AuthSession: authSession,
ControllerContext: controllerCtx,
Logger: logr.Discard(),
AuthSession: authSession,
}

g.Expect(reconciler.createAndAttachMetadata(ctx, deploymentZoneCtx, tests[0].vsphereFailureDomainSpec.Region)).NotTo(HaveOccurred())
g.Expect(reconciler.createAndAttachMetadata(ctx, deploymentZoneCtx, vsphereFailureDomain, tests[0].vsphereFailureDomainSpec.Region)).NotTo(HaveOccurred())
stdout := gbytes.NewBuffer()
g.Expect(simr.Run("tags.category.info k8s-region", stdout)).To(Succeed())
g.Expect(stdout).To(gbytes.Say("k8s-region"))
Expand All @@ -303,13 +300,12 @@ func TestVsphereDeploymentZoneReconciler_Reconcile_CreateAndAttachMetadata(t *te
}

deploymentZoneCtx := &capvcontext.VSphereDeploymentZoneContext{
ControllerContext: controllerCtx,
VSphereFailureDomain: vsphereFailureDomain,
Logger: logr.Discard(),
AuthSession: authSession,
ControllerContext: controllerCtx,
Logger: logr.Discard(),
AuthSession: authSession,
}

g.Expect(reconciler.createAndAttachMetadata(ctx, deploymentZoneCtx, tests[1].vsphereFailureDomainSpec.Zone)).NotTo(HaveOccurred())
g.Expect(reconciler.createAndAttachMetadata(ctx, deploymentZoneCtx, vsphereFailureDomain, tests[1].vsphereFailureDomainSpec.Zone)).NotTo(HaveOccurred())
stdout := gbytes.NewBuffer()
g.Expect(simr.Run("tags.category.info k8s-zone", stdout)).To(Succeed())
g.Expect(stdout).To(gbytes.Say("k8s-zone"))
Expand All @@ -324,13 +320,12 @@ func TestVsphereDeploymentZoneReconciler_Reconcile_CreateAndAttachMetadata(t *te
}

deploymentZoneCtx := &capvcontext.VSphereDeploymentZoneContext{
ControllerContext: controllerCtx,
VSphereFailureDomain: vsphereFailureDomain,
Logger: logr.Discard(),
AuthSession: authSession,
ControllerContext: controllerCtx,
Logger: logr.Discard(),
AuthSession: authSession,
}

g.Expect(reconciler.createAndAttachMetadata(ctx, deploymentZoneCtx, tests[2].vsphereFailureDomainSpec.Zone)).NotTo(HaveOccurred())
g.Expect(reconciler.createAndAttachMetadata(ctx, deploymentZoneCtx, vsphereFailureDomain, tests[2].vsphereFailureDomainSpec.Zone)).NotTo(HaveOccurred())
stdout := gbytes.NewBuffer()
g.Expect(simr.Run("tags.category.info foo", stdout)).To(Succeed())
g.Expect(stdout).To(gbytes.Say("foo"))
Expand Down
Loading

0 comments on commit 71c9d67

Please sign in to comment.