diff --git a/api/v1alpha3/types.go b/api/v1alpha3/types.go index 773a6553f..ec468f66c 100644 --- a/api/v1alpha3/types.go +++ b/api/v1alpha3/types.go @@ -6,6 +6,11 @@ type PacketResourceStatus string var ( // PacketResourceStatus is the string representing a Packet resource just created and in a provisioning state. PacketResourceStatusNew = PacketResourceStatus("new") + // PacketResourceStatusQueued is the string representing a Packet resource that is waiting in a queue to be created. + PacketResourceStatusQueued = PacketResourceStatus("queued") + // PacketResourceStatusQueued is the string representing a Packet resource + // that got picked from a worker that is not provisioning it. + PacketResourceStatusProvisioning = PacketResourceStatus("provisioning") // PacketResourceStatusRunning is the string representing a Packet resource already provisioned and in a active state. PacketResourceStatusRunning = PacketResourceStatus("active") // PacketResourceStatusErrored is the string representing a Packet resource in a errored state. diff --git a/controllers/packetcluster_controller.go b/controllers/packetcluster_controller.go index 19404e4d4..d15543f27 100644 --- a/controllers/packetcluster_controller.go +++ b/controllers/packetcluster_controller.go @@ -18,6 +18,7 @@ package controllers import ( "context" + "fmt" "time" "github.com/go-logr/logr" @@ -33,15 +34,17 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" infrastructurev1alpha3 "github.com/packethost/cluster-api-provider-packet/api/v1alpha3" + packet "github.com/packethost/cluster-api-provider-packet/pkg/cloud/packet" "github.com/packethost/cluster-api-provider-packet/pkg/cloud/packet/scope" ) // PacketClusterReconciler reconciles a PacketCluster object type PacketClusterReconciler struct { client.Client - Log logr.Logger - Recorder record.EventRecorder - Scheme *runtime.Scheme + Log logr.Logger + Recorder record.EventRecorder + Scheme *runtime.Scheme + PacketClient *packet.PacketClient } // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=packetclusters,verbs=get;list;watch;create;update;patch;delete @@ -94,9 +97,30 @@ func (r *PacketClusterReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, re } }() - // we have no setup to be done clusterScope.PacketCluster.Status.Ready = true + address, err := r.getIP(clusterScope.PacketCluster) + _, isNoMachine := err.(*MachineNotFound) + _, isNoIP := err.(*MachineNoIP) + switch { + case err != nil && isNoMachine: + logger.Info("Control plan device not found. Requeueing...") + return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil + case err != nil && isNoIP: + logger.Info("Control plan device not found. Requeueing...") + return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil + case err != nil: + logger.Error(err, "error getting a control plan ip") + return ctrl.Result{}, err + case err == nil: + clusterScope.PacketCluster.Status.APIEndpoints = []infrastructurev1alpha3.APIEndpoint{ + { + Host: address, + Port: 6443, + }, + } + } + return ctrl.Result{}, nil } @@ -111,3 +135,45 @@ func (r *PacketClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { ). Complete(r) } + +func (r *PacketClusterReconciler) getIP(cluster *infrastructurev1alpha3.PacketCluster) (string, error) { + if cluster == nil { + return "", fmt.Errorf("cannot get IP of machine in nil cluster") + } + tags := []string{ + packet.GenerateClusterTag(string(cluster.Name)), + infrastructurev1alpha3.MasterTag, + } + device, err := r.PacketClient.GetDeviceByTags(cluster.Spec.ProjectID, tags) + if err != nil { + return "", fmt.Errorf("error retrieving machine: %v", err) + } + if device == nil { + return "", &MachineNotFound{err: fmt.Sprintf("machine does not exist")} + } + if device.Network == nil || len(device.Network) == 0 || device.Network[0].Address == "" { + return "", &MachineNoIP{err: "machine does not yet have an IP address"} + } + // TODO: validate that this address exists, so we don't hit nil pointer + // TODO: check which address to return + // TODO: check address format (cidr, subnet, etc.) + return device.Network[0].Address, nil +} + +// MachineNotFound error representing that the requested device was not yet found +type MachineNotFound struct { + err string +} + +func (e *MachineNotFound) Error() string { + return e.err +} + +// MachineNoIP error representing that the requested device does not have an IP yet assigned +type MachineNoIP struct { + err string +} + +func (e *MachineNoIP) Error() string { + return e.err +} diff --git a/controllers/packetmachine_controller.go b/controllers/packetmachine_controller.go index 617022e28..1bc05c419 100644 --- a/controllers/packetmachine_controller.go +++ b/controllers/packetmachine_controller.go @@ -234,7 +234,7 @@ func (r *PacketMachineReconciler) reconcile(ctx context.Context, machineScope *s var result = ctrl.Result{} switch infrastructurev1alpha3.PacketResourceStatus(dev.State) { - case infrastructurev1alpha3.PacketResourceStatusNew: + case infrastructurev1alpha3.PacketResourceStatusNew, infrastructurev1alpha3.PacketResourceStatusQueued: machineScope.Info("Machine instance is pending", "instance-id", machineScope.GetInstanceID()) result = ctrl.Result{RequeueAfter: 10 * time.Second} case infrastructurev1alpha3.PacketResourceStatusRunning: diff --git a/main.go b/main.go index 2d65b4f8f..790f49290 100644 --- a/main.go +++ b/main.go @@ -85,10 +85,11 @@ func main() { } if err = (&controllers.PacketClusterReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("PacketCluster"), - Recorder: mgr.GetEventRecorderFor("packetcluster-controller"), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("PacketCluster"), + Recorder: mgr.GetEventRecorderFor("packetcluster-controller"), + PacketClient: client, + Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "PacketCluster") os.Exit(1) diff --git a/pkg/cloud/packet/client.go b/pkg/cloud/packet/client.go index c3da8b43a..c7f58d967 100644 --- a/pkg/cloud/packet/client.go +++ b/pkg/cloud/packet/client.go @@ -74,3 +74,17 @@ func (p *PacketClient) GetDeviceAddresses(device *packngo.Device) ([]corev1.Node } return addrs, nil } + +func (p *PacketClient) GetDeviceByTags(project string, tags []string) (*packngo.Device, error) { + devices, _, err := p.Devices.List(project, nil) + if err != nil { + return nil, fmt.Errorf("Error retrieving devices: %v", err) + } + // returns the first one that matches all of the tags + for _, device := range devices { + if ItemsInList(device.Tags, tags) { + return &device, nil + } + } + return nil, nil +} diff --git a/pkg/cloud/packet/util.go b/pkg/cloud/packet/util.go index 8607eb5a1..f525446ec 100644 --- a/pkg/cloud/packet/util.go +++ b/pkg/cloud/packet/util.go @@ -1,16 +1,40 @@ package packet -import "fmt" +import ( + "fmt" +) const ( - machineUIDTag = "cluster-api-provider-packet:machine-uid" + MachineUIDTag = "cluster-api-provider-packet:machine-uid" clusterIDTag = "cluster-api-provider-packet:cluster-id" AnnotationUID = "cluster.k8s.io/machine-uid" ) func GenerateMachineTag(ID string) string { - return fmt.Sprintf("%s:%s", machineUIDTag, ID) + return fmt.Sprintf("%s:%s", MachineUIDTag, ID) } func GenerateClusterTag(ID string) string { return fmt.Sprintf("%s:%s", clusterIDTag, ID) } + +// ItemsInList checks if all items are in the list +func ItemsInList(list []string, items []string) bool { + // convert the items against which we are mapping into a map + itemMap := map[string]bool{} + for _, elm := range items { + itemMap[elm] = false + } + // every one that is matched goes from false to true in the map + for _, elm := range list { + if _, ok := itemMap[elm]; ok { + itemMap[elm] = true + } + } + // go through the map; if any is false, return false, else all matched so return true + for _, v := range itemMap { + if !v { + return false + } + } + return true +}