| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| "v.io/v23/context" |
| "v.io/v23/security" |
| "v.io/v23/services/device" |
| ) |
| |
| const ( |
| clusterAgentServiceName = "cluster-agent" |
| clusterAgentServicePort = 8193 |
| clusterAgentApplicationName = "cluster-agentd" |
| ) |
| |
| // createClusterAgent creates a ReplicationController and a Service to run the |
| // cluster agent. |
| func createClusterAgent(ctx *context.T, config *vkubeConfig) error { |
| if err := createNamespaceIfNotExist(config.ClusterAgent.Namespace); err != nil { |
| return err |
| } |
| version := "latest" |
| if p := strings.Split(config.ClusterAgent.Image, ":"); len(p) == 2 { |
| version = p[1] |
| } |
| ca := object{ |
| "apiVersion": "v1", |
| "kind": "ReplicationController", |
| "metadata": object{ |
| "name": clusterAgentApplicationName + "-" + version, |
| "labels": object{ |
| "application": clusterAgentApplicationName, |
| }, |
| "namespace": config.ClusterAgent.Namespace, |
| }, |
| "spec": object{ |
| "replicas": 1, |
| "template": object{ |
| "metadata": object{ |
| "labels": object{ |
| "application": clusterAgentApplicationName, |
| "deployment": version, |
| }, |
| }, |
| "spec": object{ |
| "containers": []object{ |
| object{ |
| "name": "cluster-agentd", |
| "image": config.ClusterAgent.Image, |
| "ports": []object{ |
| object{ |
| "containerPort": clusterAgentServicePort, |
| }, |
| }, |
| "resources": object{ |
| "limits": object{ |
| "cpu": config.ClusterAgent.CPU, |
| "memory": config.ClusterAgent.Memory, |
| }, |
| }, |
| "volumeMounts": []object{ |
| object{ |
| "name": "data", |
| "mountPath": "/data", |
| }, |
| object{ |
| "name": "logs", |
| "mountPath": "/logs", |
| }, |
| }, |
| "env": []object{ |
| object{ |
| "name": "ROOT_BLESSINGS", |
| "value": rootBlessings(ctx), |
| }, |
| object{ |
| "name": "CLAIMER", |
| "value": clusterAgentClaimer(config), |
| }, |
| object{ |
| "name": "ADMIN", |
| "value": config.ClusterAgent.Admin, |
| }, |
| object{ |
| "name": "DATADIR", |
| "value": "/data", |
| }, |
| object{ |
| "name": "LOGDIR", |
| "value": "/logs", |
| }, |
| }, |
| }, |
| }, |
| "volumes": []interface{}{ |
| object{ |
| "name": "logs", |
| "emptyDir": object{}, |
| }, |
| }, |
| }, |
| }, |
| }, |
| } |
| if config.ClusterAgent.PersistentDisk == "" { |
| ca.append("spec.template.spec.volumes", object{ |
| "name": "data", |
| "emptyDir": object{}, |
| }) |
| } else { |
| ca.append("spec.template.spec.volumes", object{ |
| "name": "data", |
| "gcePersistentDisk": object{ |
| "pdName": config.ClusterAgent.PersistentDisk, |
| "fsType": "ext4", |
| }, |
| }) |
| } |
| |
| if out, err := kubectlCreate(ca); err != nil { |
| return fmt.Errorf("failed to create replication controller: %v\n%s\n", err, string(out)) |
| } |
| |
| svc := object{ |
| "apiVersion": "v1", |
| "kind": "Service", |
| "metadata": object{ |
| "name": clusterAgentServiceName, |
| "namespace": config.ClusterAgent.Namespace, |
| }, |
| "spec": object{ |
| "ports": []object{ |
| object{ |
| "port": clusterAgentServicePort, |
| "targetPort": clusterAgentServicePort, |
| }, |
| }, |
| "selector": object{ |
| "application": clusterAgentApplicationName, |
| }, |
| "type": "LoadBalancer", |
| }, |
| } |
| if config.ClusterAgent.ExternalIP != "" { |
| svc.set("spec.loadBalancerIP", config.ClusterAgent.ExternalIP) |
| } |
| if out, err := kubectlCreate(svc); err != nil { |
| return fmt.Errorf("failed to create service: %v\n%s\n", err, string(out)) |
| } |
| return nil |
| } |
| |
| // stopClusterAgent stops the cluster agent ReplicationController and deletes |
| // its Service. |
| func stopClusterAgent(config *vkubeConfig) error { |
| if out, err := kubectl("--namespace="+config.ClusterAgent.Namespace, "stop", "rc", "-l", "application="+clusterAgentApplicationName); err != nil { |
| return fmt.Errorf("failed to stop %s: %v: %s", clusterAgentApplicationName, err, out) |
| } |
| if out, err := kubectl("--namespace="+config.ClusterAgent.Namespace, "delete", "service", clusterAgentServiceName); err != nil { |
| return fmt.Errorf("failed to delete %s: %v: %s", clusterAgentServiceName, err, out) |
| } |
| return nil |
| } |
| |
| // clusterAgentClaimer returns the blessing name of the claimer of the cluster |
| // agent. |
| func clusterAgentClaimer(config *vkubeConfig) string { |
| p := strings.Split(config.ClusterAgent.Blessing, security.ChainSeparator) |
| return strings.Join(p[:len(p)-1], security.ChainSeparator) |
| } |
| |
| // findClusterAgent returns the external address of the cluster agent. |
| func findClusterAgent(config *vkubeConfig, includeBlessings bool) (string, error) { |
| out, err := kubectl("--namespace="+config.ClusterAgent.Namespace, "get", "service", clusterAgentServiceName, "-o", "json") |
| if err != nil { |
| return "", fmt.Errorf("failed to get info of %s: %v: %s", clusterAgentServiceName, err, out) |
| } |
| var svc object |
| if err := svc.importJSON(out); err != nil { |
| return "", fmt.Errorf("failed to parse kubectl output: %v", err) |
| } |
| ports := svc.getObjectArray("spec.ports") |
| if len(ports) == 0 { |
| return "", fmt.Errorf("service %q has no ports", clusterAgentServiceName) |
| } |
| port := ports[0].getInt("port") |
| if port < 0 { |
| return "", fmt.Errorf("service %q has no valid port: %v", clusterAgentServiceName, port) |
| } |
| ingress := svc.getObjectArray("status.loadBalancer.ingress") |
| if len(ingress) == 0 { |
| return "", fmt.Errorf("service %q has no loadbalancer ingress", clusterAgentServiceName) |
| } |
| ip := ingress[0].getString("ip") |
| if ip == "" { |
| return "", fmt.Errorf("service %q loadbalancer has no valid ip", clusterAgentServiceName) |
| } |
| if includeBlessings { |
| return fmt.Sprintf("/(%s)@%s:%d", config.ClusterAgent.Blessing, ip, port), nil |
| } |
| return fmt.Sprintf("/%s:%d", ip, port), nil |
| } |
| |
| // claimClusterAgent claims the cluster agent with the given blessing extension. |
| func claimClusterAgent(ctx *context.T, config *vkubeConfig, extension string) error { |
| addr, err := findClusterAgent(config, false) |
| if err != nil { |
| return err |
| } |
| if err := device.ClaimableClient(addr).Claim(ctx, "", &granter{extension: extension}); err != nil { |
| return err |
| } |
| return nil |
| } |