Merge pull request #239 from aaron-prindle/control-kubernetes-version

Kubernetes/localkube versioning via a flag and tests
pull/433/head
Aaron Prindle 2016-08-01 08:50:12 -07:00 committed by GitHub
commit c493cbe50d
9 changed files with 172 additions and 30 deletions

View File

@ -35,13 +35,14 @@ import (
)
var (
minikubeISO string
memory int
cpus int
disk = newUnitValue(20 * units.GB)
vmDriver string
dockerEnv []string
insecureRegistry []string
minikubeISO string
memory int
cpus int
disk = newUnitValue(20 * units.GB)
vmDriver string
dockerEnv []string
insecureRegistry []string
kubernetesVersion string
)
// startCmd represents the start command
@ -67,6 +68,9 @@ func runStart(cmd *cobra.Command, args []string) {
DockerEnv: dockerEnv,
InsecureRegistry: insecureRegistry,
}
kubernetesConfig := cluster.KubernetesConfig{
KubernetesVersion: kubernetesVersion,
}
var host *host.Host
start := func() (err error) {
@ -79,7 +83,7 @@ func runStart(cmd *cobra.Command, args []string) {
os.Exit(1)
}
if err := cluster.UpdateCluster(host.Driver); err != nil {
if err := cluster.UpdateCluster(host, host.Driver, kubernetesConfig); err != nil {
glog.Errorln("Error updating cluster: ", err)
os.Exit(1)
}
@ -157,14 +161,14 @@ func setupKubeconfig(name, server, certAuth, cliCert, cliKey string) error {
}
func init() {
startCmd.Flags().StringVarP(&minikubeISO, "iso-url", "", constants.DefaultIsoUrl, "Location of the minikube iso")
startCmd.Flags().StringVarP(&vmDriver, "vm-driver", "", constants.DefaultVMDriver, fmt.Sprintf("VM driver is one of: %v", constants.SupportedVMDrivers))
startCmd.Flags().IntVarP(&memory, "memory", "", constants.DefaultMemory, "Amount of RAM allocated to the minikube VM")
startCmd.Flags().IntVarP(&cpus, "cpus", "", constants.DefaultCPUS, "Number of CPUs allocated to the minikube VM")
startCmd.Flags().StringVar(&minikubeISO, "iso-url", constants.DefaultIsoUrl, "Location of the minikube iso")
startCmd.Flags().StringVar(&vmDriver, "vm-driver", constants.DefaultVMDriver, fmt.Sprintf("VM driver is one of: %v", constants.SupportedVMDrivers))
startCmd.Flags().IntVar(&memory, "memory", constants.DefaultMemory, "Amount of RAM allocated to the minikube VM")
startCmd.Flags().IntVar(&cpus, "cpus", constants.DefaultCPUS, "Number of CPUs allocated to the minikube VM")
diskFlag := startCmd.Flags().VarPF(disk, "disk-size", "", "Disk size allocated to the minikube VM (format: <number>[<unit>], where unit = b, k, m or g)")
diskFlag.DefValue = constants.DefaultDiskSize
startCmd.Flags().StringSliceVar(&dockerEnv, "docker-env", nil, "Environment variables to pass to the Docker daemon. (format: key=value)")
startCmd.Flags().StringSliceVar(&insecureRegistry, "insecure-registry", nil, "Insecure Docker registries to pass to the Docker daemon")
startCmd.Flags().StringVar(&kubernetesVersion, "kubernetes-version", constants.DefaultKubernetesVersion, "The kubernetes version that the minikube VM will (ex: v1.2.3) \n OR a URI which contains a localkube binary (ex: https://storage.googleapis.com/minikube/k8sReleases/v1.3.0/localkube-linux-amd64)")
RootCmd.AddCommand(startCmd)
}

View File

@ -20,6 +20,8 @@ minikube start
--docker-env=[]: Environment variables to pass to the Docker daemon. (format: key=value)
--insecure-registry=[]: Insecure Docker registries to pass to the Docker daemon
--iso-url="https://storage.googleapis.com/minikube/minikube-0.5.iso": Location of the minikube iso
--kubernetes-version="v1.3.3": The kubernetes version that the minikube VM will (ex: v1.2.3)
OR a URI which contains a localkube binary (ex: https://storage.googleapis.com/minikube/k8sReleases/v1.3.0/localkube-linux-amd64)
--memory=1024: Amount of RAM allocated to the minikube VM
--vm-driver="virtualbox": VM driver is one of: [virtualbox vmwarefusion kvm xhyve]
```

View File

@ -17,7 +17,9 @@ limitations under the License.
package cluster
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
@ -36,6 +38,7 @@ import (
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/state"
"github.com/golang/glog"
"golang.org/x/crypto/ssh"
kubeApi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
@ -177,6 +180,11 @@ type MachineConfig struct {
InsecureRegistry []string
}
// KubernetesConfig contains the parameters used to configure the VM Kubernetes.
type KubernetesConfig struct {
KubernetesVersion string
}
// StartCluster starts a k8s cluster on the specified Host.
func StartCluster(h sshAble) error {
commands := []string{stopCommand, GetStartCommand()}
@ -201,12 +209,6 @@ type fileToCopy struct {
}
var assets = []fileToCopy{
{
AssetName: "out/localkube",
TargetDir: "/usr/local/bin",
TargetName: "localkube",
Permissions: "0777",
},
{
AssetName: "deploy/iso/addon-manager.yaml",
TargetDir: "/etc/kubernetes/manifests/",
@ -227,11 +229,56 @@ var assets = []fileToCopy{
},
}
func UpdateCluster(d drivers.Driver) error {
func updateLocalkubeFromURL(config KubernetesConfig, client *ssh.Client) error {
resp := &http.Response{}
err := errors.New("")
downloader := func() (err error) {
url, err := util.GetLocalkubeDownloadURL(config.KubernetesVersion,
constants.LocalkubeLinuxFilename)
if err != nil {
return err
}
resp, err = http.Get(url)
return err
}
if err = util.Retry(5, downloader); err != nil {
return err
}
if err = sshutil.Transfer(resp.Body, int(resp.ContentLength), "/usr/local/bin",
"localkube", "0777", client); err != nil {
return err
}
return nil
}
func updateLocalkubeFromAsset(client *ssh.Client) error {
contents, err := Asset("out/localkube")
if err != nil {
glog.Infof("Error loading asset out/localkube: %s", err)
return err
}
if err := sshutil.Transfer(bytes.NewReader(contents), len(contents), "/usr/local/bin",
"localkube", "0777", client); err != nil {
return err
}
return nil
}
func UpdateCluster(h sshAble, d drivers.Driver, config KubernetesConfig) error {
client, err := sshutil.NewSSHClient(d)
if err != nil {
return err
}
if localkubeURLWasSpecified(config) {
if err = updateLocalkubeFromURL(config, client); err != nil {
return err
}
} else {
if err = updateLocalkubeFromAsset(client); err != nil {
return err
}
}
for _, a := range assets {
contents, err := Asset(a.AssetName)
@ -240,13 +287,18 @@ func UpdateCluster(d drivers.Driver) error {
return err
}
if err := sshutil.Transfer(contents, a.TargetDir, a.TargetName, a.Permissions, client); err != nil {
if err := sshutil.Transfer(bytes.NewReader(contents), len(contents), a.TargetDir, a.TargetName, a.Permissions, client); err != nil {
return err
}
}
return nil
}
func localkubeURLWasSpecified(config KubernetesConfig) bool {
// see if flag is different than default -> it was passed by user
return config.KubernetesVersion != constants.DefaultKubernetesVersion
}
// SetupCerts gets the generated credentials required to talk to the APIServer.
func SetupCerts(d drivers.Driver) error {
localPath := constants.Minipath
@ -280,7 +332,7 @@ func SetupCerts(d drivers.Driver) error {
if strings.HasSuffix(cert, ".key") {
perms = "0600"
}
if err := sshutil.Transfer(data, util.DefaultCertPath, cert, perms, client); err != nil {
if err := sshutil.Transfer(bytes.NewReader(data), len(data), util.DefaultCertPath, cert, perms, client); err != nil {
return err
}
}

View File

@ -19,7 +19,10 @@ package cluster
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
@ -502,13 +505,14 @@ func TestGetServiceURLWithoutNodePort(t *testing.T) {
}
}
func TestUpdate(t *testing.T) {
func TestUpdateDefault(t *testing.T) {
s, _ := tests.NewSSHServer()
port, err := s.Start()
if err != nil {
t.Fatalf("Error starting ssh server: %s", err)
}
h := tests.NewMockHost()
d := &tests.MockDriver{
Port: port,
BaseDriver: drivers.BaseDriver{
@ -517,15 +521,67 @@ func TestUpdate(t *testing.T) {
},
}
if err := UpdateCluster(d); err != nil {
kubernetesConfig := KubernetesConfig{
KubernetesVersion: constants.DefaultKubernetesVersion,
}
if err := UpdateCluster(h, d, kubernetesConfig); err != nil {
t.Fatalf("Error updating cluster: %s", err)
}
transferred := s.Transfers.Bytes()
//test that kube-add on assets are transferred properly
for _, a := range assets {
contents, _ := Asset(a.AssetName)
if !bytes.Contains(transferred, contents) {
t.Fatalf("File not copied. Expected transfers to contain: %s. It was: %s", contents, transferred)
}
}
//test that localkube is transferred properly
contents, _ := Asset("out/localkube")
if !bytes.Contains(transferred, contents) {
t.Fatalf("File not copied. Expected transfers to contain: %s. It was: %s", contents, transferred)
}
}
var testLocalkubeBin = "hello"
type K8sVersionHandlerCorrect struct{}
func (h *K8sVersionHandlerCorrect) ServeHTTP(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, testLocalkubeBin)
}
func TestUpdateKubernetesVersion(t *testing.T) {
s, _ := tests.NewSSHServer()
port, err := s.Start()
if err != nil {
t.Fatalf("Error starting ssh server: %s", err)
}
h := tests.NewMockHost()
d := &tests.MockDriver{
Port: port,
BaseDriver: drivers.BaseDriver{
IPAddress: "127.0.0.1",
SSHKeyPath: "",
},
}
handler := &K8sVersionHandlerCorrect{}
server := httptest.NewServer(handler)
kubernetesConfig := KubernetesConfig{
KubernetesVersion: server.URL,
}
if err := UpdateCluster(h, d, kubernetesConfig); err != nil {
t.Fatalf("Error updating cluster: %s", err)
}
transferred := s.Transfers.Bytes()
//test that localkube is transferred properly
contents := []byte(testLocalkubeBin)
if !bytes.Contains(transferred, contents) {
t.Fatalf("File not copied. Expected transfers to contain: %s. It was: %s", contents, transferred)
}
}

View File

@ -31,7 +31,6 @@ var startCommandFmtStr = `
# Run with nohup so it stays up. Redirect logs to useful places.
sudo sh -c 'PATH=/usr/local/sbin:$PATH nohup /usr/local/bin/localkube %s --generate-certs=false --logtostderr=true > %s 2> %s < /dev/null &'
`
var logsCommand = fmt.Sprintf("tail -n +1 %s %s", constants.RemoteLocalKubeErrPath, constants.RemoteLocalKubeOutPath)
func GetStartCommand() string {

View File

@ -21,6 +21,7 @@ import (
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/util/homedir"
"k8s.io/kubernetes/pkg/version"
)
// MachineName is the name to use for the VM.
@ -59,7 +60,14 @@ const (
DefaultVMDriver = "virtualbox"
)
var DefaultKubernetesVersion = version.Get().GitVersion
const (
RemoteLocalKubeErrPath = "/var/lib/localkube/localkube.err"
RemoteLocalKubeOutPath = "/var/lib/localkube/localkube.out"
)
var ConfigFilePath = MakeMiniPath("config")
var LocalkubeDownloadURLPrefix = "https://storage.googleapis.com/minikube/k8sReleases/"
var LocalkubeLinuxFilename = "localkube-linux-amd64"

View File

@ -17,7 +17,6 @@ limitations under the License.
package sshutil
import (
"bytes"
"fmt"
"io"
"path/filepath"
@ -60,7 +59,7 @@ func NewSSHClient(d drivers.Driver) (*ssh.Client, error) {
}
// Transfer uses an SSH session to copy a file to the remote machine.
func Transfer(data []byte, remotedir, filename string, perm string, c *ssh.Client) error {
func Transfer(reader io.Reader, readerLen int, remotedir, filename string, perm string, c *ssh.Client) error {
// Delete the old file first. This makes sure permissions get reset.
deleteCmd := fmt.Sprintf("sudo rm -f %s", filepath.Join(remotedir, filename))
mkdirCmd := fmt.Sprintf("sudo mkdir -p %s", remotedir)
@ -86,9 +85,8 @@ func Transfer(data []byte, remotedir, filename string, perm string, c *ssh.Clien
go func() {
defer wg.Done()
defer w.Close()
header := fmt.Sprintf("C%s %d %s\n", perm, len(data), filename)
header := fmt.Sprintf("C%s %d %s\n", perm, readerLen, filename)
fmt.Fprint(w, header)
reader := bytes.NewReader(data)
io.Copy(w, reader)
fmt.Fprint(w, "\x00")
}()

View File

@ -17,6 +17,7 @@ limitations under the License.
package sshutil
import (
"bytes"
"testing"
"github.com/docker/machine/libmachine/drivers"
@ -109,7 +110,8 @@ func TestTransfer(t *testing.T) {
}
dest := "bar"
if err := Transfer([]byte("testcontents"), "/tmp", dest, "0777", c); err != nil {
contents := []byte("testcontents")
if err := Transfer(bytes.NewReader(contents), len(contents), "/tmp", dest, "0777", c); err != nil {
t.Fatalf("Unexpected error: %s", err)
}
}

View File

@ -19,8 +19,12 @@ package util
import (
"fmt"
"io"
"net/url"
"os"
"strings"
"time"
"k8s.io/minikube/pkg/minikube/constants"
)
// Until endlessly loops the provided function until a message is received on the done channel.
@ -76,3 +80,20 @@ func RetryAfter(attempts int, callback func() error, d time.Duration) (err error
}
return err
}
func GetLocalkubeDownloadURL(versionOrURL string, filename string) (string, error) {
urlObj, err := url.Parse(versionOrURL)
if err != nil {
return "", err
}
if urlObj.IsAbs() {
// scheme was specified in input, is a valid URI.
// http.Get will catch unsupported schemes
return versionOrURL, nil
}
if !strings.HasPrefix(versionOrURL, "v") {
// no 'v' prefix in input, need to prepend it to version
versionOrURL = "v" + versionOrURL
}
return fmt.Sprintf("%s%s/%s", constants.LocalkubeDownloadURLPrefix, versionOrURL, filename), nil
}