Files
virtual-kubelet/providers/huawei/cci.go
Brian Goff a54753cb82 Move around some packages (#658)
* Move tracing exporter registration

This doesn't belong in the library and should be configured by the
consumer of the opencensus package.

* Rename `vkublet` package to `node`

`vkubelet` does not convey any information to the consumers of the
package.
Really it would be nice to move this package to the root of the repo,
but then you wind up with... interesting... import semantics due to the
repo name... and after thinking about it some, a subpackage is really
not so bad as long as it has a name that convey's some information.

`node` was chosen since this package deals with all the semantics of
operating a node in Kubernetes.
2019-06-12 13:11:49 +01:00

452 lines
13 KiB
Go

package huawei
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/virtual-kubelet/virtual-kubelet/errdefs"
"github.com/virtual-kubelet/virtual-kubelet/manager"
"github.com/virtual-kubelet/virtual-kubelet/node/api"
"github.com/virtual-kubelet/virtual-kubelet/providers/huawei/auth"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
const (
podAnnotationNamespaceKey = "virtual-kubelet-namespace"
podAnnotationPodNameKey = "virtual-kubelet-podname"
podAnnotationClusterNameKey = "virtual-kubelet-clustername"
podAnnotationUIDkey = "virtual-kubelet-uid"
podAnnotationNodeName = "virtual-kubelet-nodename"
podAnnotationCreationTimestamp = "virtual-kubelet-creationtimestamp"
)
var defaultApiEndpoint string = "https://cciback.cn-north-1.huaweicloud.com"
// CCIProvider implements the virtual-kubelet provider interface and communicates with Huawei's CCI APIs.
type CCIProvider struct {
appKey string
appSecret string
apiEndpoint string
region string
service string
project string
internalIP string
daemonEndpointPort int32
nodeName string
operatingSystem string
client *Client
resourceManager *manager.ResourceManager
cpu string
memory string
pods string
}
// Client represents the client config for Huawei.
type Client struct {
Signer auth.Signer
HTTPClient http.Client
}
// NewCCIProvider creates a new CCI provider.
func NewCCIProvider(config string, rm *manager.ResourceManager, nodeName, operatingSystem string, internalIP string, daemonEndpointPort int32) (*CCIProvider, error) {
p := CCIProvider{}
if config != "" {
f, err := os.Open(config)
if err != nil {
return nil, err
}
defer f.Close()
if err := p.loadConfig(f); err != nil {
return nil, err
}
}
if appKey := os.Getenv("CCI_APP_KEP"); appKey != "" {
p.appKey = appKey
}
if p.appKey == "" {
return nil, errors.New("AppKey can not be empty please set CCI_APP_KEP")
}
if appSecret := os.Getenv("CCI_APP_SECRET"); appSecret != "" {
p.appSecret = appSecret
}
if p.appSecret == "" {
return nil, errors.New("AppSecret can not be empty please set CCI_APP_SECRET")
}
p.client = new(Client)
p.client.Signer = &auth.SignerHws{
AppKey: p.appKey,
AppSecret: p.appSecret,
Region: p.region,
Service: p.service,
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
p.client.HTTPClient = http.Client{
Transport: tr,
}
p.resourceManager = rm
p.apiEndpoint = defaultApiEndpoint
p.nodeName = nodeName
p.operatingSystem = operatingSystem
p.internalIP = internalIP
p.daemonEndpointPort = daemonEndpointPort
if err := p.createProject(); err != nil {
return nil, err
}
return &p, nil
}
func (p *CCIProvider) createProject() error {
// Create the createProject request url
uri := p.apiEndpoint + "/api/v1/namespaces"
// build the request
project := &v1.Namespace{
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: p.project,
},
}
var bodyReader io.Reader
body, err := json.Marshal(project)
if err != nil {
return err
}
if body != nil {
bodyReader = bytes.NewReader(body)
}
r, err := http.NewRequest("POST", uri, bodyReader)
if err != nil {
return err
}
if err = p.signRequest(r); err != nil {
return fmt.Errorf("Sign the request failed: %v", err)
}
_, err = p.client.HTTPClient.Do(r)
return err
}
func (p *CCIProvider) signRequest(r *http.Request) error {
r.Header.Add("content-type", "application/json; charset=utf-8")
if err := p.client.Signer.Sign(r); err != nil {
return fmt.Errorf("Sign the request failed: %v", err)
}
return nil
}
func (p *CCIProvider) setPodAnnotations(pod *v1.Pod) {
metav1.SetMetaDataAnnotation(&pod.ObjectMeta, podAnnotationNamespaceKey, pod.Namespace)
metav1.SetMetaDataAnnotation(&pod.ObjectMeta, podAnnotationClusterNameKey, pod.ClusterName)
metav1.SetMetaDataAnnotation(&pod.ObjectMeta, podAnnotationPodNameKey, pod.Name)
metav1.SetMetaDataAnnotation(&pod.ObjectMeta, podAnnotationUIDkey, string(pod.UID))
metav1.SetMetaDataAnnotation(&pod.ObjectMeta, podAnnotationNodeName, pod.Spec.NodeName)
metav1.SetMetaDataAnnotation(&pod.ObjectMeta, podAnnotationCreationTimestamp, pod.CreationTimestamp.String())
pod.Namespace = p.project
pod.Name = pod.Namespace + "-" + pod.Name
pod.UID = ""
pod.Spec.NodeName = ""
pod.CreationTimestamp = metav1.Time{}
}
func (p *CCIProvider) deletePodAnnotations(pod *v1.Pod) error {
pod.Name = pod.Annotations[podAnnotationPodNameKey]
pod.Namespace = pod.Annotations[podAnnotationNamespaceKey]
pod.UID = types.UID(pod.Annotations[podAnnotationUIDkey])
pod.ClusterName = pod.Annotations[podAnnotationClusterNameKey]
pod.Spec.NodeName = pod.Annotations[podAnnotationNodeName]
if pod.Annotations[podAnnotationCreationTimestamp] != "" {
t, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", pod.Annotations[podAnnotationCreationTimestamp])
if err != nil {
return err
}
podCreationTimestamp := metav1.NewTime(t)
pod.CreationTimestamp = podCreationTimestamp
}
delete(pod.Annotations, podAnnotationPodNameKey)
delete(pod.Annotations, podAnnotationNamespaceKey)
delete(pod.Annotations, podAnnotationUIDkey)
delete(pod.Annotations, podAnnotationClusterNameKey)
delete(pod.Annotations, podAnnotationNodeName)
delete(pod.Annotations, podAnnotationCreationTimestamp)
pod.Annotations = nil
return nil
}
// CreatePod takes a Kubernetes Pod and deploys it within the huawei CCI provider.
func (p *CCIProvider) CreatePod(ctx context.Context, pod *v1.Pod) error {
// Create the createPod request url
p.setPodAnnotations(pod)
uri := p.apiEndpoint + "/api/v1/namespaces/" + p.project + "/pods"
// build the request
var bodyReader io.Reader
body, err := json.Marshal(pod)
if err != nil {
return err
}
if body != nil {
bodyReader = bytes.NewReader(body)
}
r, err := http.NewRequest("POST", uri, bodyReader)
if err != nil {
return err
}
if err = p.signRequest(r); err != nil {
return fmt.Errorf("Sign the request failed: %v", err)
}
_, err = p.client.HTTPClient.Do(r)
return err
}
// UpdatePod takes a Kubernetes Pod and updates it within the huawei CCI provider.
func (p *CCIProvider) UpdatePod(ctx context.Context, pod *v1.Pod) error {
return nil
}
// DeletePod takes a Kubernetes Pod and deletes it from the huawei CCI provider.
func (p *CCIProvider) DeletePod(ctx context.Context, pod *v1.Pod) error {
// Create the deletePod request url
podName := pod.Namespace + "-" + pod.Name
uri := p.apiEndpoint + "/api/v1/namespaces/" + p.project + "/pods/" + podName
// build the request
r, err := http.NewRequest("DELETE", uri, nil)
if err != nil {
return err
}
if err = p.signRequest(r); err != nil {
return fmt.Errorf("Sign the request failed: %v", err)
}
resp, err := p.client.HTTPClient.Do(r)
if err != nil {
return err
}
return errorFromResponse(resp)
}
func errorFromResponse(resp *http.Response) error {
if resp.StatusCode < 400 {
return nil
}
body, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 16*1024))
err := fmt.Errorf("error during http request, status=%d: %q", resp.StatusCode, string(body))
switch resp.StatusCode {
case http.StatusNotFound:
return errdefs.AsNotFound(err)
default:
return err
}
}
// GetPod retrieves a pod by name from the huawei CCI provider.
func (p *CCIProvider) GetPod(ctx context.Context, namespace, name string) (*v1.Pod, error) {
// Create the getPod request url
podName := namespace + "-" + name
uri := p.apiEndpoint + "/api/v1/namespaces/" + p.project + "/pods/" + podName
r, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, fmt.Errorf("Create get POD request failed: %v", err)
}
if err = p.signRequest(r); err != nil {
return nil, fmt.Errorf("Sign the request failed: %v", err)
}
resp, err := p.client.HTTPClient.Do(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var pod v1.Pod
if err = json.Unmarshal(body, &pod); err != nil {
return nil, err
}
if err := p.deletePodAnnotations(&pod); err != nil {
return nil, err
}
return &pod, nil
}
// GetContainerLogs retrieves the logs of a container by name from the huawei CCI provider.
func (p *CCIProvider) GetContainerLogs(ctx context.Context, namespace, podName, containerName string, opts api.ContainerLogOpts) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), nil
}
// Get full pod name as defined in the provider context
// TODO: Implementation
func (p *CCIProvider) GetPodFullName(namespace string, pod string) string {
return ""
}
// RunInContainer executes a command in a container in the pod, copying data
// between in/out/err and the container's stdin/stdout/stderr.
// TODO: Implementation
func (p *CCIProvider) RunInContainer(ctx context.Context, namespace, name, container string, cmd []string, attach api.AttachIO) error {
log.Printf("receive ExecInContainer %q\n", container)
return nil
}
// GetPodStatus retrieves the status of a pod by name from the huawei CCI provider.
func (p *CCIProvider) GetPodStatus(ctx context.Context, namespace, name string) (*v1.PodStatus, error) {
pod, err := p.GetPod(ctx, namespace, name)
if err != nil {
return nil, err
}
if pod == nil {
return nil, nil
}
return &pod.Status, nil
}
// GetPods retrieves a list of all pods running on the huawei CCI provider.
func (p *CCIProvider) GetPods(ctx context.Context) ([]*v1.Pod, error) {
// Create the getPod request url
uri := p.apiEndpoint + "/api/v1/namespaces/" + p.project + "/pods"
r, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, fmt.Errorf("Create get POD request failed: %v", err)
}
if err = p.signRequest(r); err != nil {
return nil, fmt.Errorf("Sign the request failed: %v", err)
}
resp, err := p.client.HTTPClient.Do(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var pods []*v1.Pod
if err = json.Unmarshal(body, &pods); err != nil {
return nil, err
}
for _, pod := range pods {
if err := p.deletePodAnnotations(pod); err != nil {
return nil, err
}
}
return pods, nil
}
// Capacity returns a resource list with the capacity constraints of the huawei CCI provider.
func (p *CCIProvider) Capacity(ctx context.Context) v1.ResourceList {
return v1.ResourceList{
"cpu": resource.MustParse(p.cpu),
"memory": resource.MustParse(p.memory),
"pods": resource.MustParse(p.pods),
}
}
// NodeConditions returns a list of conditions (Ready, OutOfDisk, etc), which is
// polled periodically to update the node status within Kubernetes.
func (p *CCIProvider) NodeConditions(ctx context.Context) []v1.NodeCondition {
// TODO: Make these dynamic and augment with custom CCI specific conditions of interest
return []v1.NodeCondition{
{
Type: "Ready",
Status: v1.ConditionTrue,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "KubeletReady",
Message: "kubelet is ready.",
},
{
Type: "OutOfDisk",
Status: v1.ConditionFalse,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "KubeletHasSufficientDisk",
Message: "kubelet has sufficient disk space available",
},
{
Type: "MemoryPressure",
Status: v1.ConditionFalse,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "KubeletHasSufficientMemory",
Message: "kubelet has sufficient memory available",
},
{
Type: "DiskPressure",
Status: v1.ConditionFalse,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "KubeletHasNoDiskPressure",
Message: "kubelet has no disk pressure",
},
{
Type: "NetworkUnavailable",
Status: v1.ConditionFalse,
LastHeartbeatTime: metav1.Now(),
LastTransitionTime: metav1.Now(),
Reason: "RouteCreated",
Message: "RouteController created a route",
},
}
}
// NodeAddresses returns a list of addresses for the node status
// within Kubernetes.
func (p *CCIProvider) NodeAddresses(ctx context.Context) []v1.NodeAddress {
// TODO: Make these dynamic and augment with custom CCI specific conditions of interest
return []v1.NodeAddress{
{
Type: "InternalIP",
Address: p.internalIP,
},
}
}
// NodeDaemonEndpoints returns NodeDaemonEndpoints for the node status
// within Kubernetes.
func (p *CCIProvider) NodeDaemonEndpoints(ctx context.Context) *v1.NodeDaemonEndpoints {
return &v1.NodeDaemonEndpoints{
KubeletEndpoint: v1.DaemonEndpoint{
Port: p.daemonEndpointPort,
},
}
}
// OperatingSystem returns the operating system the huawei CCI provider is for.
func (p *CCIProvider) OperatingSystem() string {
return p.operatingSystem
}