diff --git a/pkg/commands/status_discovery.go b/pkg/commands/status_discovery.go index 0a90b29705..0122e8b1ae 100644 --- a/pkg/commands/status_discovery.go +++ b/pkg/commands/status_discovery.go @@ -94,5 +94,9 @@ func (s *CloudDiscoveryStatusStore) FindClusterStatus(cluster *kops.Cluster) (*k if aliCloud, ok := cloud.(aliup.ALICloud); ok { return aliCloud.FindClusterStatus(cluster) } + + if osCloud, ok := cloud.(openstack.OpenstackCloud); ok { + return osCloud.FindClusterStatus(cluster) + } return nil, fmt.Errorf("Etcd Status not implemented for %T", cloud) } diff --git a/upup/pkg/fi/cloudup/openstack/cloud.go b/upup/pkg/fi/cloudup/openstack/cloud.go index 1856357424..e7f5600523 100644 --- a/upup/pkg/fi/cloudup/openstack/cloud.go +++ b/upup/pkg/fi/cloudup/openstack/cloud.go @@ -59,6 +59,7 @@ import ( const TagNameEtcdClusterPrefix = "k8s.io/etcd/" const TagNameRolePrefix = "k8s.io/role/" const TagClusterName = "KubernetesCluster" +const TagRoleMaster = "master" // ErrNotFound is used to inform that the object is not found var ErrNotFound = "Resource not found" @@ -184,6 +185,8 @@ type OpenstackCloud interface { GetApiIngressStatus(cluster *kops.Cluster) ([]kops.ApiIngressStatus, error) + FindClusterStatus(cluster *kops.Cluster) (*kops.ClusterStatus, error) + // DefaultInstanceType determines a suitable instance type for the specified instance group DefaultInstanceType(cluster *kops.Cluster, ig *kops.InstanceGroup) (string, error) diff --git a/upup/pkg/fi/cloudup/openstack/status.go b/upup/pkg/fi/cloudup/openstack/status.go new file mode 100644 index 0000000000..1ea01ce80b --- /dev/null +++ b/upup/pkg/fi/cloudup/openstack/status.go @@ -0,0 +1,94 @@ +/* +Copyright 2019 The Kubernetes Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package openstack + +import ( + "fmt" + "strings" + + "github.com/golang/glog" + cinderv2 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" + "k8s.io/kops/pkg/apis/kops" + "k8s.io/kops/protokube/pkg/etcd" + "k8s.io/kops/upup/pkg/fi" +) + +// FindClusterStatus discovers the status of the cluster, by looking for the tagged etcd volumes +func (c *openstackCloud) FindClusterStatus(cluster *kops.Cluster) (*kops.ClusterStatus, error) { + etcdStatus, err := findEtcdStatus(c, cluster) + if err != nil { + return nil, err + } + status := &kops.ClusterStatus{ + EtcdClusters: etcdStatus, + } + glog.V(2).Infof("Cluster status (from cloud): %v", fi.DebugAsJsonString(status)) + return status, nil +} + +// findEtcdStatus discovers the status of etcd, by looking for the tagged etcd volumes +func findEtcdStatus(c *openstackCloud, cluster *kops.Cluster) ([]kops.EtcdClusterStatus, error) { + statusMap := make(map[string]*kops.EtcdClusterStatus) + glog.V(2).Infof("Querying Openstack for etcd volumes") + opt := cinderv2.ListOpts{ + Metadata: c.tags, + } + volumes, err := c.ListVolumes(opt) + if err != nil { + return nil, fmt.Errorf("error describing volumes: %v", err) + } + + for _, volume := range volumes { + volumeID := volume.ID + + etcdClusterName := "" + var etcdClusterSpec *etcd.EtcdClusterSpec + + master := false + for k, v := range volume.Metadata { + if strings.HasPrefix(k, TagNameEtcdClusterPrefix) { + etcdClusterName := strings.TrimPrefix(k, TagNameEtcdClusterPrefix) + etcdClusterSpec, err = etcd.ParseEtcdClusterSpec(etcdClusterName, v) + if err != nil { + return nil, fmt.Errorf("error parsing etcd cluster tag %q on volume %q: %v", v, volumeID, err) + } + } else if k == TagNameRolePrefix+TagRoleMaster { + master = true + } + } + if etcdClusterSpec == nil || !master { + continue + } + + etcdClusterName = etcdClusterSpec.ClusterKey + status := statusMap[etcdClusterName] + if status == nil { + status = &kops.EtcdClusterStatus{ + Name: etcdClusterName, + } + statusMap[etcdClusterName] = status + } + + memberName := etcdClusterSpec.NodeName + status.Members = append(status.Members, &kops.EtcdMemberStatus{ + Name: memberName, + VolumeId: volume.ID, + }) + } + var status []kops.EtcdClusterStatus + for _, v := range statusMap { + status = append(status, *v) + } + return status, nil +}