Skip to content

Commit

Permalink
make start.config non-mutating
Browse files Browse the repository at this point in the history
  • Loading branch information
deads2k committed Feb 24, 2015
1 parent e028737 commit 93d27aa
Show file tree
Hide file tree
Showing 7 changed files with 1,138 additions and 838 deletions.
122 changes: 122 additions & 0 deletions pkg/cmd/server/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package server

import (
"fmt"
_ "net/http/pprof"

"github.com/golang/glog"
"github.com/spf13/cobra"

"github.com/openshift/origin/pkg/cmd/flagtypes"
cmdutil "github.com/openshift/origin/pkg/cmd/util"
"github.com/openshift/origin/pkg/cmd/util/docker"
"github.com/openshift/origin/pkg/cmd/util/variable"
)

const longCommandDesc = `
Start an OpenShift server
This command helps you launch an OpenShift server. The default mode is all-in-one, which allows
you to run all of the components of an OpenShift system on a server with Docker. Running
$ openshift start
will start OpenShift listening on all interfaces, launch an etcd server to store persistent
data, and launch the Kubernetes system components. The server will run in the foreground until
you terminate the process.
Note: starting OpenShift without passing the --master address will attempt to find the IP
address that will be visible inside running Docker containers. This is not always successful,
so if you have problems tell OpenShift what public address it will be via --master=<ip>.
You may also pass an optional argument to the start command to start OpenShift in one of the
following roles:
$ openshift start master --nodes=<host1,host2,host3,...>
Launches the server and control plane for OpenShift. You may pass a list of the node
hostnames you want to use, or create nodes via the REST API or 'openshift kube'.
$ openshift start node --master=<masterIP>
Launches a new node and attempts to connect to the master on the provided IP.
You may also pass --etcd=<address> to connect to an external etcd server instead of running an
integrated instance, or --kubernetes=<addr> and --kubeconfig=<path> to connect to an existing
Kubernetes cluster.
`

// NewCommandStartServer provides a CLI handler for 'start' command
func NewCommandStartServer(name string) *cobra.Command {
hostname, err := defaultHostname()
if err != nil {
hostname = "localhost"
glog.Warningf("Unable to lookup hostname, using %q: %v", hostname, err)
}

cfg := &config{
Docker: docker.NewHelper(),

MasterAddr: flagtypes.Addr{Value: "localhost:8443", DefaultScheme: "https", DefaultPort: 8443, AllowPrefix: true}.Default(),
BindAddr: flagtypes.Addr{Value: "0.0.0.0:8443", DefaultScheme: "https", DefaultPort: 8443, AllowPrefix: true}.Default(),
EtcdAddr: flagtypes.Addr{Value: "0.0.0.0:4001", DefaultScheme: "http", DefaultPort: 4001}.Default(),
KubernetesAddr: flagtypes.Addr{DefaultScheme: "https", DefaultPort: 8443}.Default(),
PortalNet: flagtypes.DefaultIPNet("172.30.17.0/24"),
MasterPublicAddr: flagtypes.Addr{Value: "localhost:8443", DefaultScheme: "https", DefaultPort: 8443, AllowPrefix: true}.Default(),
KubernetesPublicAddr: flagtypes.Addr{Value: "localhost:8443", DefaultScheme: "https", DefaultPort: 8443, AllowPrefix: true}.Default(),

ImageTemplate: variable.NewDefaultImageTemplate(),

Hostname: hostname,
NodeList: flagtypes.StringList{"127.0.0.1"},
}

cmd := &cobra.Command{
Use: fmt.Sprintf("%s [master|node]", name),
Short: "Launch OpenShift",
Long: longCommandDesc,
Run: func(c *cobra.Command, args []string) {
cfg.Complete(args)

if err := start(*cfg, args); err != nil {
glog.Fatal(err)
}
},
}

flag := cmd.Flags()

flag.Var(&cfg.BindAddr, "listen", "The address to listen for connections on (host, host:port, or URL).")
flag.Var(&cfg.MasterAddr, "master", "The master address for use by OpenShift components (host, host:port, or URL). Scheme and port default to the --listen scheme and port.")
flag.Var(&cfg.MasterPublicAddr, "public-master", "The master address for use by public clients, if different (host, host:port, or URL). Defaults to same as --master.")
flag.Var(&cfg.EtcdAddr, "etcd", "The address of the etcd server (host, host:port, or URL). If specified, no built-in etcd will be started.")
flag.Var(&cfg.KubernetesAddr, "kubernetes", "The address of the Kubernetes server (host, host:port, or URL). If specified, no Kubernetes components will be started.")
flag.Var(&cfg.KubernetesPublicAddr, "public-kubernetes", "The Kubernetes server address for use by public clients, if different. (host, host:port, or URL). Defaults to same as --kubernetes.")
flag.Var(&cfg.PortalNet, "portal-net", "A CIDR notation IP range from which to assign portal IPs. This must not overlap with any IP ranges assigned to nodes for pods.")

flag.StringVar(&cfg.ImageTemplate.Format, "images", cfg.ImageTemplate.Format, "When fetching images used by the cluster for important components, use this format on both master and nodes. The latest release will be used by default.")
flag.BoolVar(&cfg.ImageTemplate.Latest, "latest-images", cfg.ImageTemplate.Latest, "If true, attempt to use the latest images for the cluster instead of the latest release.")

flag.StringVar(&cfg.VolumeDir, "volume-dir", "openshift.local.volumes", "The volume storage directory.")
flag.StringVar(&cfg.EtcdDir, "etcd-dir", "openshift.local.etcd", "The etcd data directory.")
flag.StringVar(&cfg.CertDir, "cert-dir", "openshift.local.certificates", "The certificate data directory.")

flag.StringVar(&cfg.Hostname, "hostname", cfg.Hostname, "The hostname to identify this node with the master.")
flag.Var(&cfg.NodeList, "nodes", "The hostnames of each node. This currently must be specified up front. Comma delimited list")
flag.Var(&cfg.CORSAllowedOrigins, "cors-allowed-origins", "List of allowed origins for CORS, comma separated. An allowed origin can be a regular expression to support subdomain matching. CORS is enabled for localhost, 127.0.0.1, and the asset server by default.")

cfg.ClientConfig = cmdutil.DefaultClientConfig(flag)

cfg.Docker.InstallFlags(flag)

return cmd
}

const startMaster = "master"
const startNode = "startNode"

// Complete takes the args and fills in information for the start config
func (cfg *config) Complete(args []string) {
cfg.ExplicitStartMaster = (len(args) == 1) && (args[0] == startMaster)
cfg.ExplicitStartNode = (len(args) == 1) && (args[0] == startNode)
}
257 changes: 257 additions & 0 deletions pkg/cmd/server/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
package server

import (
"fmt"
"net"
_ "net/http/pprof"
"net/url"
"os/exec"
"strconv"
"strings"
"time"

"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
etcdclient "github.com/coreos/go-etcd/etcd"
"github.com/golang/glog"

"github.com/openshift/origin/pkg/api/latest"
"github.com/openshift/origin/pkg/cmd/flagtypes"
"github.com/openshift/origin/pkg/cmd/util"
"github.com/openshift/origin/pkg/cmd/util/docker"
"github.com/openshift/origin/pkg/cmd/util/variable"
)

// config is a struct that the command stores flag values into.
type config struct {
Docker *docker.Helper

ExplicitStartNode bool
ExplicitStartMaster bool

MasterAddr flagtypes.Addr
BindAddr flagtypes.Addr
EtcdAddr flagtypes.Addr
KubernetesAddr flagtypes.Addr
PortalNet flagtypes.IPNet
// addresses for external clients
MasterPublicAddr flagtypes.Addr
KubernetesPublicAddr flagtypes.Addr

ImageFormat string
LatestReleaseImages bool

ImageTemplate variable.ImageTemplate

Hostname string
VolumeDir string

EtcdDir string

CertDir string

StorageVersion string

NodeList flagtypes.StringList

// ClientConfig is used when connecting to Kubernetes from the master, or
// when connecting to the master from a detached node. If the server is an
// all-in-one, this value is not used.
ClientConfig clientcmd.ClientConfig

CORSAllowedOrigins flagtypes.StringList
}

// GetMasterAddress checks for an unset master address and then attempts to use the first
// public IPv4 non-loopback address registered on this host. It will also update the
// EtcdAddr after if it was not provided.
// TODO: make me IPv6 safe
func (cfg config) GetMasterAddress() (*url.URL, error) {
if cfg.MasterAddr.Provided {
return cfg.MasterAddr.URL, nil
}

if cfg.IsStartMaster() {
// If the user specifies a bind address, and the master is not provided, use the bind port by default
port := cfg.MasterAddr.Port
if cfg.BindAddr.Provided {
port = cfg.BindAddr.Port
}

// If the user specifies a bind address, and the master is not provided, use the bind scheme by default
scheme := cfg.MasterAddr.URL.Scheme
if cfg.BindAddr.Provided {
scheme = cfg.BindAddr.URL.Scheme
}

// use the default ip address for the system
addr, err := util.DefaultLocalIP4()
if err != nil {
return nil, fmt.Errorf("Unable to find the public address of this master: %v", err)
}

masterAddr := scheme + "://" + net.JoinHostPort(addr.String(), strconv.Itoa(port))
return url.Parse(masterAddr)
}

// if we didn't specify and we aren't starting the master, read .kubeconfig to locate the master
// TODO client config currently doesn't let you override the defaults
// so it is defaulting to https://localhost:8443 for MasterAddr if
// it isn't set by --master or --kubeconfig
config, err := cfg.ClientConfig.ClientConfig()
if err != nil {
return nil, err
}
return url.Parse(config.Host)
}

func (cfg config) GetMasterPublicAddress() (*url.URL, error) {
if cfg.MasterPublicAddr.Provided {
return cfg.MasterPublicAddr.URL, nil
}

return cfg.GetMasterAddress()
}

func (cfg config) GetEtcdAddress() (*url.URL, error) {
if cfg.EtcdAddr.Provided {
return cfg.EtcdAddr.URL, nil
}

// Etcd should be reachable on the same address that the master is (for simplicity)
masterAddr, err := cfg.GetMasterAddress()
if err != nil {
return nil, err
}

etcdAddr := net.JoinHostPort(getHost(*masterAddr), strconv.Itoa(cfg.EtcdAddr.DefaultPort))
return url.Parse("http://" + etcdAddr)
}

func (cfg config) GetKubernetesAddress() (*url.URL, error) {
if cfg.KubernetesAddr.Provided {
return cfg.KubernetesAddr.URL, nil
}

return cfg.GetMasterAddress()
}

func (cfg config) GetKubernetesPublicAddress() (*url.URL, error) {
if cfg.KubernetesPublicAddr.Provided {
return cfg.KubernetesPublicAddr.URL, nil
}

return cfg.GetMasterPublicAddress()
}

func (cfg config) GetNodeList() []string {
nodeList := []string{}
for _, curr := range cfg.NodeList {
nodeList = append(curr)
}

if len(nodeList) == 1 && nodeList[0] == "127.0.0.1" {
nodeList[0] = cfg.Hostname
}
for i, s := range nodeList {
s = strings.ToLower(s)
nodeList[i] = s
glog.Infof(" Node: %s", s)
}

return nodeList
}

func (cfg config) IsStartNode() bool {
if cfg.ExplicitStartMaster {
return false
}

return true
}

func (cfg config) IsStartMaster() bool {
if cfg.ExplicitStartNode {
return false
}

return true
}

func (cfg config) IsStartKube() bool {
if cfg.ExplicitStartNode {
return false
}
if cfg.KubernetesAddr.Provided {
return false
}

return true
}

func (cfg config) IsStartEtcd() bool {
if cfg.ExplicitStartNode {
return false
}
if cfg.EtcdAddr.Provided {
return false
}

return true
}

// getEtcdClient creates an etcd client based on the provided config and waits
// until etcd server is reachable. It errors out and exits if the server cannot
// be reached for a certain amount of time.
func (cfg config) getEtcdClient() (*etcdclient.Client, error) {
address, err := cfg.GetEtcdAddress()
if err != nil {
return nil, err
}
etcdServers := []string{address.String()}
etcdClient := etcdclient.NewClient(etcdServers)

for i := 0; ; i++ {
_, err := etcdClient.Get("/", false, false)
if err == nil || tools.IsEtcdNotFound(err) {
break
}
if i > 100 {
return nil, fmt.Errorf("Could not reach etcd: %v", err)
}
time.Sleep(50 * time.Millisecond)
}

return etcdClient, nil
}

// newOpenShiftEtcdHelper returns an EtcdHelper for the provided arguments or an error if the version
// is incorrect.
func (cfg config) newOpenShiftEtcdHelper() (helper tools.EtcdHelper, err error) {
// Connect and setup etcd interfaces
client, err := cfg.getEtcdClient()
if err != nil {
return tools.EtcdHelper{}, err
}

version := cfg.StorageVersion
if len(version) == 0 {
version = latest.Version
}
interfaces, err := latest.InterfacesFor(version)
if err != nil {
return helper, err
}
return tools.EtcdHelper{client, interfaces.Codec, tools.RuntimeVersionAdapter{interfaces.MetadataAccessor}}, nil
}

// defaultHostname returns the default hostname for this system.
func defaultHostname() (string, error) {
// Note: We use exec here instead of os.Hostname() because we
// want the FQDN, and this is the easiest way to get it.
fqdn, err := exec.Command("hostname", "-f").Output()
if err != nil {
return "", fmt.Errorf("Couldn't determine hostname: %v", err)
}
return strings.TrimSpace(string(fqdn)), nil
}
Loading

0 comments on commit 93d27aa

Please sign in to comment.