| ... | ... |
@@ -9,6 +9,7 @@ import ( |
| 9 | 9 |
_ "github.com/openshift/origin/pkg/deploy/api" |
| 10 | 10 |
_ "github.com/openshift/origin/pkg/image/api" |
| 11 | 11 |
_ "github.com/openshift/origin/pkg/template/api" |
| 12 |
+ _ "github.com/openshift/origin/pkg/route/api" |
|
| 12 | 13 |
) |
| 13 | 14 |
|
| 14 | 15 |
// Codec is the identity codec for this package - it can only convert itself |
| ... | ... |
@@ -9,6 +9,7 @@ import ( |
| 9 | 9 |
_ "github.com/openshift/origin/pkg/deploy/api/v1beta1" |
| 10 | 10 |
_ "github.com/openshift/origin/pkg/image/api/v1beta1" |
| 11 | 11 |
_ "github.com/openshift/origin/pkg/template/api/v1beta1" |
| 12 |
+ _ "github.com/openshift/origin/pkg/route/api/v1beta1" |
|
| 12 | 13 |
) |
| 13 | 14 |
|
| 14 | 15 |
// Codec encodes internal objects to the v1beta1 scheme |
| ... | ... |
@@ -12,6 +12,7 @@ import ( |
| 12 | 12 |
buildapi "github.com/openshift/origin/pkg/build/api" |
| 13 | 13 |
deployapi "github.com/openshift/origin/pkg/deploy/api" |
| 14 | 14 |
imageapi "github.com/openshift/origin/pkg/image/api" |
| 15 |
+ routeapi "github.com/openshift/origin/pkg/route/api" |
|
| 15 | 16 |
) |
| 16 | 17 |
|
| 17 | 18 |
// Interface exposes methods on OpenShift resources. |
| ... | ... |
@@ -23,6 +24,7 @@ type Interface interface {
|
| 23 | 23 |
ImageRepositoryMappingInterface |
| 24 | 24 |
DeploymentInterface |
| 25 | 25 |
DeploymentConfigInterface |
| 26 |
+ RouteInterface |
|
| 26 | 27 |
} |
| 27 | 28 |
|
| 28 | 29 |
// BuildInterface exposes methods on Build resources. |
| ... | ... |
@@ -81,6 +83,16 @@ type DeploymentInterface interface {
|
| 81 | 81 |
DeleteDeployment(string) error |
| 82 | 82 |
} |
| 83 | 83 |
|
| 84 |
+// RouteInterface exposes methods on Route resources |
|
| 85 |
+type RouteInterface interface {
|
|
| 86 |
+ ListRoutes(selector labels.Selector) (*routeapi.RouteList, error) |
|
| 87 |
+ GetRoute(routeID string) (*routeapi.Route, error) |
|
| 88 |
+ CreateRoute(route *routeapi.Route) (*routeapi.Route, error) |
|
| 89 |
+ UpdateRoute(route *routeapi.Route) (*routeapi.Route, error) |
|
| 90 |
+ DeleteRoute(routeID string) error |
|
| 91 |
+ WatchRoutes(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) |
|
| 92 |
+} |
|
| 93 |
+ |
|
| 84 | 94 |
// Client is an OpenShift client object |
| 85 | 95 |
type Client struct {
|
| 86 | 96 |
*kubeclient.RESTClient |
| ... | ... |
@@ -295,3 +307,43 @@ func (c *Client) UpdateDeployment(deployment *deployapi.Deployment) (result *dep |
| 295 | 295 |
func (c *Client) DeleteDeployment(id string) error {
|
| 296 | 296 |
return c.Delete().Path("deployments").Path(id).Do().Error()
|
| 297 | 297 |
} |
| 298 |
+ |
|
| 299 |
+// ListRoutes takes a selector, and returns the list of routes that match that selector |
|
| 300 |
+func (c *Client) ListRoutes(selector labels.Selector) (result *routeapi.RouteList, err error) {
|
|
| 301 |
+ err = c.Get().Path("routes").SelectorParam("labels", selector).Do().Into(result)
|
|
| 302 |
+ return |
|
| 303 |
+} |
|
| 304 |
+ |
|
| 305 |
+// GetRoute takes the name of the route, and returns the corresponding Route object, and an error if it occurs |
|
| 306 |
+func (c *Client) GetRoute(name string) (result *routeapi.Route, err error) {
|
|
| 307 |
+ err = c.Get().Path("routes").Path(name).Do().Into(result)
|
|
| 308 |
+ return |
|
| 309 |
+} |
|
| 310 |
+ |
|
| 311 |
+// DeleteRoute takes the name of the route, and returns an error if one occurs |
|
| 312 |
+func (c *Client) DeleteRoute(name string) error {
|
|
| 313 |
+ return c.Delete().Path("routes").Path(name).Do().Error()
|
|
| 314 |
+} |
|
| 315 |
+ |
|
| 316 |
+// CreateRoute takes the representation of a route. Returns the server's representation of the route, and an error, if it occurs |
|
| 317 |
+func (c *Client) CreateRoute(route *routeapi.Route) (result *routeapi.Route, err error) {
|
|
| 318 |
+ err = c.Post().Path("routes").Body(route).Do().Into(result)
|
|
| 319 |
+ return |
|
| 320 |
+} |
|
| 321 |
+ |
|
| 322 |
+// UpdateRoute takes the representation of a route to update. Returns the server's representation of the route, and an error, if it occurs |
|
| 323 |
+func (c *Client) UpdateRoute(route *routeapi.Route) (result *routeapi.Route, err error) {
|
|
| 324 |
+ err = c.Put().Path("routes").Path(route.ID).Body(route).Do().Into(result)
|
|
| 325 |
+ return |
|
| 326 |
+} |
|
| 327 |
+ |
|
| 328 |
+// WatchRoutes returns a watch.Interface that watches the requested routes. |
|
| 329 |
+func (c *Client) WatchRoutes(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
|
|
| 330 |
+ return c.Get(). |
|
| 331 |
+ Path("watch").
|
|
| 332 |
+ Path("routes").
|
|
| 333 |
+ UintParam("resourceVersion", resourceVersion).
|
|
| 334 |
+ SelectorParam("labels", label).
|
|
| 335 |
+ SelectorParam("fields", field).
|
|
| 336 |
+ Watch() |
|
| 337 |
+} |
| ... | ... |
@@ -7,6 +7,7 @@ import ( |
| 7 | 7 |
buildapi "github.com/openshift/origin/pkg/build/api" |
| 8 | 8 |
deployapi "github.com/openshift/origin/pkg/deploy/api" |
| 9 | 9 |
imageapi "github.com/openshift/origin/pkg/image/api" |
| 10 |
+ routeapi "github.com/openshift/origin/pkg/route/api" |
|
| 10 | 11 |
) |
| 11 | 12 |
|
| 12 | 13 |
type FakeAction struct {
|
| ... | ... |
@@ -160,3 +161,33 @@ func (c *Fake) DeleteDeployment(id string) error {
|
| 160 | 160 |
c.Actions = append(c.Actions, FakeAction{Action: "delete-deployment"})
|
| 161 | 161 |
return nil |
| 162 | 162 |
} |
| 163 |
+ |
|
| 164 |
+func (c *Fake) ListRoutes(selector labels.Selector) (*routeapi.RouteList, error) {
|
|
| 165 |
+ c.Actions = append(c.Actions, FakeAction{Action: "list-routes"})
|
|
| 166 |
+ return &routeapi.RouteList{}, nil
|
|
| 167 |
+} |
|
| 168 |
+ |
|
| 169 |
+func (c *Fake) GetRoute(id string) (*routeapi.Route, error) {
|
|
| 170 |
+ c.Actions = append(c.Actions, FakeAction{Action: "get-route"})
|
|
| 171 |
+ return &routeapi.Route{}, nil
|
|
| 172 |
+} |
|
| 173 |
+ |
|
| 174 |
+func (c *Fake) CreateRoute(route *routeapi.Route) (*routeapi.Route, error) {
|
|
| 175 |
+ c.Actions = append(c.Actions, FakeAction{Action: "create-route"})
|
|
| 176 |
+ return &routeapi.Route{}, nil
|
|
| 177 |
+} |
|
| 178 |
+ |
|
| 179 |
+func (c *Fake) UpdateRoute(route *routeapi.Route) (*routeapi.Route, error) {
|
|
| 180 |
+ c.Actions = append(c.Actions, FakeAction{Action: "update-route"})
|
|
| 181 |
+ return &routeapi.Route{}, nil
|
|
| 182 |
+} |
|
| 183 |
+ |
|
| 184 |
+func (c *Fake) DeleteRoute(id string) error {
|
|
| 185 |
+ c.Actions = append(c.Actions, FakeAction{Action: "delete-route"})
|
|
| 186 |
+ return nil |
|
| 187 |
+} |
|
| 188 |
+ |
|
| 189 |
+func (c *Fake) WatchRoutes(field, label labels.Selector, resourceVersion uint64) (watch.Interface, error) {
|
|
| 190 |
+ c.Actions = append(c.Actions, FakeAction{Action: "watch-routes"})
|
|
| 191 |
+ return nil, nil |
|
| 192 |
+} |
| ... | ... |
@@ -44,11 +44,13 @@ import ( |
| 44 | 44 |
. "github.com/openshift/origin/pkg/cmd/client/api" |
| 45 | 45 |
"github.com/openshift/origin/pkg/cmd/client/build" |
| 46 | 46 |
"github.com/openshift/origin/pkg/cmd/client/image" |
| 47 |
+ "github.com/openshift/origin/pkg/cmd/client/route" |
|
| 47 | 48 |
"github.com/openshift/origin/pkg/config" |
| 48 | 49 |
configapi "github.com/openshift/origin/pkg/config/api" |
| 49 | 50 |
deployapi "github.com/openshift/origin/pkg/deploy/api" |
| 50 | 51 |
deployclient "github.com/openshift/origin/pkg/deploy/client" |
| 51 | 52 |
imageapi "github.com/openshift/origin/pkg/image/api" |
| 53 |
+ routeapi "github.com/openshift/origin/pkg/route/api" |
|
| 52 | 54 |
) |
| 53 | 55 |
|
| 54 | 56 |
type KubeConfig struct {
|
| ... | ... |
@@ -126,6 +128,7 @@ var parser = kubecfg.NewParser(map[string]runtime.Object{
|
| 126 | 126 |
"config": &configapi.Config{},
|
| 127 | 127 |
"deployments": &deployapi.Deployment{},
|
| 128 | 128 |
"deploymentConfigs": &deployapi.DeploymentConfig{},
|
| 129 |
+ "routes": &routeapi.Route{},
|
|
| 129 | 130 |
}) |
| 130 | 131 |
|
| 131 | 132 |
func prettyWireStorage() string {
|
| ... | ... |
@@ -275,6 +278,7 @@ func (c *KubeConfig) Run() {
|
| 275 | 275 |
"imageRepositoryMappings": {"ImageRepositoryMapping", client.RESTClient, latest.Codec},
|
| 276 | 276 |
"deployments": {"Deployment", client.RESTClient, latest.Codec},
|
| 277 | 277 |
"deploymentConfigs": {"DeploymentConfig", client.RESTClient, latest.Codec},
|
| 278 |
+ "routes": {"Route", client.RESTClient, latest.Codec},
|
|
| 278 | 279 |
} |
| 279 | 280 |
|
| 280 | 281 |
matchFound := c.executeConfigRequest(method, clients) || c.executeTemplateRequest(method, client) || c.executeControllerRequest(method, kubeClient) || c.executeAPIRequest(method, clients) |
| ... | ... |
@@ -520,6 +524,7 @@ func humanReadablePrinter() *kubecfg.HumanReadablePrinter {
|
| 520 | 520 |
build.RegisterPrintHandlers(printer) |
| 521 | 521 |
image.RegisterPrintHandlers(printer) |
| 522 | 522 |
deployclient.RegisterPrintHandlers(printer) |
| 523 |
+ route.RegisterPrintHandlers(printer) |
|
| 523 | 524 |
|
| 524 | 525 |
return printer |
| 525 | 526 |
} |
| 526 | 527 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,33 @@ |
| 0 |
+package route |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg" |
|
| 7 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/openshift/origin/pkg/route/api" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+var routeColumns = []string{"ID", "Host/Port", "Path", "Service", "Labels"}
|
|
| 13 |
+ |
|
| 14 |
+// RegisterPrintHandlers registers HumanReadablePrinter handlers |
|
| 15 |
+func RegisterPrintHandlers(printer *kubecfg.HumanReadablePrinter) {
|
|
| 16 |
+ printer.Handler(routeColumns, printRoute) |
|
| 17 |
+ printer.Handler(routeColumns, printRouteList) |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+func printRoute(route *api.Route, w io.Writer) error {
|
|
| 21 |
+ _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", route.ID, route.Host, route.Path, route.ServiceName, labels.Set(route.Labels)) |
|
| 22 |
+ return err |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+func printRouteList(routeList *api.RouteList, w io.Writer) error {
|
|
| 26 |
+ for _, route := range routeList.Items {
|
|
| 27 |
+ if err := printRoute(&route, w); err != nil {
|
|
| 28 |
+ return err |
|
| 29 |
+ } |
|
| 30 |
+ } |
|
| 31 |
+ return nil |
|
| 32 |
+} |
| ... | ... |
@@ -50,6 +50,8 @@ import ( |
| 50 | 50 |
"github.com/openshift/origin/pkg/image/registry/image" |
| 51 | 51 |
"github.com/openshift/origin/pkg/image/registry/imagerepository" |
| 52 | 52 |
"github.com/openshift/origin/pkg/image/registry/imagerepositorymapping" |
| 53 |
+ routeregistry "github.com/openshift/origin/pkg/route/registry/route" |
|
| 54 |
+ routeetcd "github.com/openshift/origin/pkg/route/registry/etcd" |
|
| 53 | 55 |
"github.com/openshift/origin/pkg/template" |
| 54 | 56 |
"github.com/openshift/origin/pkg/version" |
| 55 | 57 |
|
| ... | ... |
@@ -57,6 +59,7 @@ import ( |
| 57 | 57 |
_ "github.com/openshift/origin/pkg/config/api/v1beta1" |
| 58 | 58 |
_ "github.com/openshift/origin/pkg/image/api/v1beta1" |
| 59 | 59 |
_ "github.com/openshift/origin/pkg/template/api/v1beta1" |
| 60 |
+ _ "github.com/openshift/origin/pkg/route/api/v1beta1" |
|
| 60 | 61 |
) |
| 61 | 62 |
|
| 62 | 63 |
func NewCommandStartAllInOne(name string) *cobra.Command {
|
| ... | ... |
@@ -214,6 +217,7 @@ func (c *config) runApiserver() {
|
| 214 | 214 |
buildRegistry := buildetcd.New(etcdHelper) |
| 215 | 215 |
imageRegistry := imageetcd.New(etcdHelper) |
| 216 | 216 |
deployEtcd := deployetcd.New(etcdHelper) |
| 217 |
+ routeEtcd := routeetcd.New(etcdHelper) |
|
| 217 | 218 |
|
| 218 | 219 |
// initialize OpenShift API |
| 219 | 220 |
storage := map[string]apiserver.RESTStorage{
|
| ... | ... |
@@ -225,6 +229,7 @@ func (c *config) runApiserver() {
|
| 225 | 225 |
"deployments": deployregistry.NewREST(deployEtcd), |
| 226 | 226 |
"deploymentConfigs": deployconfigregistry.NewREST(deployEtcd), |
| 227 | 227 |
"templateConfigs": template.NewStorage(), |
| 228 |
+ "routes": routeregistry.NewREST(routeEtcd), |
|
| 228 | 229 |
} |
| 229 | 230 |
|
| 230 | 231 |
osMux := http.NewServeMux() |
| 231 | 232 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,15 @@ |
| 0 |
+package api |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+func init() {
|
|
| 7 |
+ api.Scheme.AddKnownTypes("",
|
|
| 8 |
+ &Route{},
|
|
| 9 |
+ &RouteList{},
|
|
| 10 |
+ ) |
|
| 11 |
+} |
|
| 12 |
+ |
|
| 13 |
+func (*Route) IsAnAPIObject() {}
|
|
| 14 |
+func (*RouteList) IsAnAPIObject() {}
|
| 0 | 15 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,27 @@ |
| 0 |
+package api |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ kubeapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+// Route encapsulates the inputs needed to connect a DNS/alias to a service proxy. |
|
| 7 |
+type Route struct {
|
|
| 8 |
+ kubeapi.JSONBase `json:",inline" yaml:",inline"` |
|
| 9 |
+ |
|
| 10 |
+ // Required: Alias/DNS that points to the service |
|
| 11 |
+ // Can be host or host:port |
|
| 12 |
+ // host and port are combined to follow the net/url URL struct |
|
| 13 |
+ Host string `json:"host" yaml:"host"` |
|
| 14 |
+ // Optional: Path that the router watches for, to route traffic for to the service |
|
| 15 |
+ Path string `json:"path,omitempty" yaml:"path,omitempty"` |
|
| 16 |
+ |
|
| 17 |
+ // the name of the service that this route points to |
|
| 18 |
+ ServiceName string `json:"serviceName" yaml:"serviceName"` |
|
| 19 |
+ Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+// RouteList is a collection of Routes. |
|
| 23 |
+type RouteList struct {
|
|
| 24 |
+ kubeapi.JSONBase `json:",inline" yaml:",inline"` |
|
| 25 |
+ Items []Route `json:"items,omitempty" yaml:"items,omitempty"` |
|
| 26 |
+} |
| 0 | 27 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,15 @@ |
| 0 |
+package v1beta1 |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+func init() {
|
|
| 7 |
+ api.Scheme.AddKnownTypes("v1beta1",
|
|
| 8 |
+ &Route{},
|
|
| 9 |
+ &RouteList{},
|
|
| 10 |
+ ) |
|
| 11 |
+} |
|
| 12 |
+ |
|
| 13 |
+func (*Route) IsAnAPIObject() {}
|
|
| 14 |
+func (*RouteList) IsAnAPIObject() {}
|
| 0 | 15 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,27 @@ |
| 0 |
+package v1beta1 |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ v1beta1 "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+// Route encapsulates the inputs needed to connect a DNS/alias to a service proxy. |
|
| 7 |
+type Route struct {
|
|
| 8 |
+ v1beta1.JSONBase `json:",inline" yaml:",inline"` |
|
| 9 |
+ |
|
| 10 |
+ // Required: Alias/DNS that points to the service |
|
| 11 |
+ // Can be host or host:port |
|
| 12 |
+ // host and port are combined to follow the net/url URL struct |
|
| 13 |
+ Host string `json:"host" yaml:"host"` |
|
| 14 |
+ // Optional: Path that the router watches for, to route traffic for to the service |
|
| 15 |
+ Path string `json:"path,omitempty" yaml:"path,omitempty"` |
|
| 16 |
+ |
|
| 17 |
+ // the name of the service that this route points to |
|
| 18 |
+ ServiceName string `json:"serviceName" yaml:"serviceName"` |
|
| 19 |
+ Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+// RouteList is a collection of Routes. |
|
| 23 |
+type RouteList struct {
|
|
| 24 |
+ v1beta1.JSONBase `json:",inline" yaml:",inline"` |
|
| 25 |
+ Items []Route `json:"items,omitempty" yaml:"items,omitempty"` |
|
| 26 |
+} |
| 0 | 27 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,19 @@ |
| 0 |
+package validation |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ errs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" |
|
| 4 |
+ routeapi "github.com/openshift/origin/pkg/route/api" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+// ValidateRoute tests if required fields in the route are set. |
|
| 8 |
+func ValidateRoute(route *routeapi.Route) errs.ErrorList {
|
|
| 9 |
+ result := errs.ErrorList{}
|
|
| 10 |
+ |
|
| 11 |
+ if len(route.Host) == 0 {
|
|
| 12 |
+ result = append(result, errs.NewFieldRequired("host", ""))
|
|
| 13 |
+ } |
|
| 14 |
+ if len(route.ServiceName) == 0 {
|
|
| 15 |
+ result = append(result, errs.NewFieldRequired("serviceName", ""))
|
|
| 16 |
+ } |
|
| 17 |
+ return result |
|
| 18 |
+} |
| 0 | 19 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,22 @@ |
| 0 |
+/* |
|
| 1 |
+Package route provides support for managing and watching routes. |
|
| 2 |
+It defines a Route resource type, along with associated storage. |
|
| 3 |
+ |
|
| 4 |
+A Route object allows the user to specify a DNS / alias for a Kubernetes service. |
|
| 5 |
+It stores the ID of the Service (ServiceName) and the DNS/alias (Name). |
|
| 6 |
+The Route can be used to specify just the DNS/alias or it could also include |
|
| 7 |
+port and/or the path. |
|
| 8 |
+ |
|
| 9 |
+The Route model includes the following attributes to specify the frontend URL: |
|
| 10 |
+ - Host: Alias/DNS that points to the service. Can be host or host:port |
|
| 11 |
+ - Path: Path allows the router to perform fine-grained routing |
|
| 12 |
+ |
|
| 13 |
+The Route resources can be used by routers and load balancers to route external inbound |
|
| 14 |
+traffic. The proxy is expected to have frontend mappings for the Route.Name in its |
|
| 15 |
+configuration. For its endpoints, a proxy could either forward the traffic to the |
|
| 16 |
+Kubernetes Service port and let it do the load balancing and routing. Alternately, |
|
| 17 |
+a more meaningful implementation of a router could take the endpoints for the service |
|
| 18 |
+and route/load balance the incoming requests to the corresponding service endpoints. |
|
| 19 |
+*/ |
|
| 20 |
+ |
|
| 21 |
+package route |
| 0 | 22 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,89 @@ |
| 0 |
+package etcd |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd" |
|
| 6 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" |
|
| 7 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" |
|
| 8 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/openshift/origin/pkg/route/api" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+// Etcd implements route.Registry backed by etcd. |
|
| 14 |
+type Etcd struct {
|
|
| 15 |
+ tools.EtcdHelper |
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+// New creates an etcd registry. |
|
| 19 |
+func New(helper tools.EtcdHelper) *Etcd {
|
|
| 20 |
+ return &Etcd{
|
|
| 21 |
+ EtcdHelper: helper, |
|
| 22 |
+ } |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+func makeRouteKey(id string) string {
|
|
| 26 |
+ return "/routes/" + id |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+// ListRoutes obtains a list of Routes. |
|
| 30 |
+func (registry *Etcd) ListRoutes(selector labels.Selector) (*api.RouteList, error) {
|
|
| 31 |
+ allRoutes := api.RouteList{}
|
|
| 32 |
+ err := registry.ExtractList("/routes", &allRoutes.Items, &allRoutes.ResourceVersion)
|
|
| 33 |
+ if err != nil {
|
|
| 34 |
+ return nil, err |
|
| 35 |
+ } |
|
| 36 |
+ filtered := []api.Route{}
|
|
| 37 |
+ for _, route := range allRoutes.Items {
|
|
| 38 |
+ if selector.Matches(labels.Set(route.Labels)) {
|
|
| 39 |
+ filtered = append(filtered, route) |
|
| 40 |
+ } |
|
| 41 |
+ } |
|
| 42 |
+ allRoutes.Items = filtered |
|
| 43 |
+ return &allRoutes, nil |
|
| 44 |
+ |
|
| 45 |
+} |
|
| 46 |
+ |
|
| 47 |
+// GetRoute gets a specific Route specified by its ID. |
|
| 48 |
+func (registry *Etcd) GetRoute(routeID string) (*api.Route, error) {
|
|
| 49 |
+ route := api.Route{}
|
|
| 50 |
+ err := registry.ExtractObj(makeRouteKey(routeID), &route, false) |
|
| 51 |
+ if err != nil {
|
|
| 52 |
+ return nil, etcderr.InterpretGetError(err, "route", routeID) |
|
| 53 |
+ } |
|
| 54 |
+ return &route, nil |
|
| 55 |
+} |
|
| 56 |
+ |
|
| 57 |
+// CreateRoute creates a new Route. |
|
| 58 |
+func (registry *Etcd) CreateRoute(route *api.Route) error {
|
|
| 59 |
+ err := registry.CreateObj(makeRouteKey(route.ID), route) |
|
| 60 |
+ return etcderr.InterpretCreateError(err, "route", route.ID) |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+// UpdateRoute replaces an existing Route. |
|
| 64 |
+func (registry *Etcd) UpdateRoute(route *api.Route) error {
|
|
| 65 |
+ err := registry.SetObj(makeRouteKey(route.ID), route) |
|
| 66 |
+ return etcderr.InterpretUpdateError(err, "route", route.ID) |
|
| 67 |
+} |
|
| 68 |
+ |
|
| 69 |
+// DeleteRoute deletes a Route specified by its ID. |
|
| 70 |
+func (registry *Etcd) DeleteRoute(routeID string) error {
|
|
| 71 |
+ key := makeRouteKey(routeID) |
|
| 72 |
+ err := registry.Delete(key, true) |
|
| 73 |
+ return etcderr.InterpretDeleteError(err, "route", routeID) |
|
| 74 |
+} |
|
| 75 |
+ |
|
| 76 |
+// WatchRoutes begins watching for new, changed, or deleted route configurations. |
|
| 77 |
+func (registry *Etcd) WatchRoutes(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
|
|
| 78 |
+ if !label.Empty() {
|
|
| 79 |
+ return nil, fmt.Errorf("label selectors are not supported on routes yet")
|
|
| 80 |
+ } |
|
| 81 |
+ if value, found := field.RequiresExactMatch("ID"); found {
|
|
| 82 |
+ return registry.Watch(makeRouteKey(value), resourceVersion) |
|
| 83 |
+ } |
|
| 84 |
+ if field.Empty() {
|
|
| 85 |
+ return registry.WatchList("/routes", resourceVersion, tools.Everything)
|
|
| 86 |
+ } |
|
| 87 |
+ return nil, fmt.Errorf("only the 'ID' and default (everything) field selectors are supported")
|
|
| 88 |
+} |
| 0 | 89 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,264 @@ |
| 0 |
+package etcd |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ kubeapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 7 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" |
|
| 8 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" |
|
| 9 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
|
| 10 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" |
|
| 11 |
+ "github.com/coreos/go-etcd/etcd" |
|
| 12 |
+ |
|
| 13 |
+ "github.com/openshift/origin/pkg/api/latest" |
|
| 14 |
+ "github.com/openshift/origin/pkg/route/api" |
|
| 15 |
+ _ "github.com/openshift/origin/pkg/route/api/v1beta1" |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+func NewTestEtcd(client tools.EtcdClient) *Etcd {
|
|
| 19 |
+ return New(tools.EtcdHelper{client, latest.Codec, latest.ResourceVersioner})
|
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+func TestEtcdListEmptyRoutes(t *testing.T) {
|
|
| 23 |
+ fakeClient := tools.NewFakeEtcdClient(t) |
|
| 24 |
+ key := "/routes" |
|
| 25 |
+ fakeClient.Data[key] = tools.EtcdResponseWithError{
|
|
| 26 |
+ R: &etcd.Response{
|
|
| 27 |
+ Node: &etcd.Node{
|
|
| 28 |
+ Nodes: []*etcd.Node{},
|
|
| 29 |
+ }, |
|
| 30 |
+ }, |
|
| 31 |
+ E: nil, |
|
| 32 |
+ } |
|
| 33 |
+ registry := NewTestEtcd(fakeClient) |
|
| 34 |
+ routes, err := registry.ListRoutes(labels.Everything()) |
|
| 35 |
+ if err != nil {
|
|
| 36 |
+ t.Errorf("unexpected error: %v", err)
|
|
| 37 |
+ } |
|
| 38 |
+ |
|
| 39 |
+ if len(routes.Items) != 0 {
|
|
| 40 |
+ t.Errorf("Unexpected routes list: %#v", routes)
|
|
| 41 |
+ } |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+func TestEtcdListErrorRoutes(t *testing.T) {
|
|
| 45 |
+ fakeClient := tools.NewFakeEtcdClient(t) |
|
| 46 |
+ key := "/routes" |
|
| 47 |
+ fakeClient.Data[key] = tools.EtcdResponseWithError{
|
|
| 48 |
+ R: &etcd.Response{
|
|
| 49 |
+ Node: nil, |
|
| 50 |
+ }, |
|
| 51 |
+ E: fmt.Errorf("some error"),
|
|
| 52 |
+ } |
|
| 53 |
+ registry := NewTestEtcd(fakeClient) |
|
| 54 |
+ routes, err := registry.ListRoutes(labels.Everything()) |
|
| 55 |
+ if err == nil {
|
|
| 56 |
+ t.Error("unexpected nil error")
|
|
| 57 |
+ } |
|
| 58 |
+ |
|
| 59 |
+ if routes != nil {
|
|
| 60 |
+ t.Errorf("Unexpected non-nil routes: %#v", routes)
|
|
| 61 |
+ } |
|
| 62 |
+} |
|
| 63 |
+ |
|
| 64 |
+func TestEtcdListEverythingRoutes(t *testing.T) {
|
|
| 65 |
+ fakeClient := tools.NewFakeEtcdClient(t) |
|
| 66 |
+ key := "/routes" |
|
| 67 |
+ fakeClient.Data[key] = tools.EtcdResponseWithError{
|
|
| 68 |
+ R: &etcd.Response{
|
|
| 69 |
+ Node: &etcd.Node{
|
|
| 70 |
+ Nodes: []*etcd.Node{
|
|
| 71 |
+ {
|
|
| 72 |
+ Value: runtime.EncodeOrDie(latest.Codec, &api.Route{JSONBase: kubeapi.JSONBase{ID: "foo"}}),
|
|
| 73 |
+ }, |
|
| 74 |
+ {
|
|
| 75 |
+ Value: runtime.EncodeOrDie(latest.Codec, &api.Route{JSONBase: kubeapi.JSONBase{ID: "bar"}}),
|
|
| 76 |
+ }, |
|
| 77 |
+ }, |
|
| 78 |
+ }, |
|
| 79 |
+ }, |
|
| 80 |
+ E: nil, |
|
| 81 |
+ } |
|
| 82 |
+ registry := NewTestEtcd(fakeClient) |
|
| 83 |
+ routes, err := registry.ListRoutes(labels.Everything()) |
|
| 84 |
+ if err != nil {
|
|
| 85 |
+ t.Errorf("unexpected error: %v", err)
|
|
| 86 |
+ } |
|
| 87 |
+ |
|
| 88 |
+ if len(routes.Items) != 2 || routes.Items[0].ID != "foo" || routes.Items[1].ID != "bar" {
|
|
| 89 |
+ t.Errorf("Unexpected routes list: %#v", routes)
|
|
| 90 |
+ } |
|
| 91 |
+} |
|
| 92 |
+ |
|
| 93 |
+func TestEtcdListFilteredRoutes(t *testing.T) {
|
|
| 94 |
+ fakeClient := tools.NewFakeEtcdClient(t) |
|
| 95 |
+ key := "/routes" |
|
| 96 |
+ fakeClient.Data[key] = tools.EtcdResponseWithError{
|
|
| 97 |
+ R: &etcd.Response{
|
|
| 98 |
+ Node: &etcd.Node{
|
|
| 99 |
+ Nodes: []*etcd.Node{
|
|
| 100 |
+ {
|
|
| 101 |
+ Value: runtime.EncodeOrDie(latest.Codec, &api.Route{
|
|
| 102 |
+ JSONBase: kubeapi.JSONBase{ID: "foo"},
|
|
| 103 |
+ Labels: map[string]string{"env": "prod"},
|
|
| 104 |
+ }), |
|
| 105 |
+ }, |
|
| 106 |
+ {
|
|
| 107 |
+ Value: runtime.EncodeOrDie(latest.Codec, &api.Route{
|
|
| 108 |
+ JSONBase: kubeapi.JSONBase{ID: "bar"},
|
|
| 109 |
+ Labels: map[string]string{"env": "dev"},
|
|
| 110 |
+ }), |
|
| 111 |
+ }, |
|
| 112 |
+ }, |
|
| 113 |
+ }, |
|
| 114 |
+ }, |
|
| 115 |
+ E: nil, |
|
| 116 |
+ } |
|
| 117 |
+ registry := NewTestEtcd(fakeClient) |
|
| 118 |
+ routes, err := registry.ListRoutes(labels.SelectorFromSet(labels.Set{"env": "dev"}))
|
|
| 119 |
+ if err != nil {
|
|
| 120 |
+ t.Errorf("unexpected error: %v", err)
|
|
| 121 |
+ } |
|
| 122 |
+ |
|
| 123 |
+ if len(routes.Items) != 1 || routes.Items[0].ID != "bar" {
|
|
| 124 |
+ t.Errorf("Unexpected routes list: %#v", routes)
|
|
| 125 |
+ } |
|
| 126 |
+} |
|
| 127 |
+ |
|
| 128 |
+func TestEtcdGetRoutes(t *testing.T) {
|
|
| 129 |
+ fakeClient := tools.NewFakeEtcdClient(t) |
|
| 130 |
+ fakeClient.Set("/routes/foo", runtime.EncodeOrDie(latest.Codec, &api.Route{JSONBase: kubeapi.JSONBase{ID: "foo"}}), 0)
|
|
| 131 |
+ registry := NewTestEtcd(fakeClient) |
|
| 132 |
+ route, err := registry.GetRoute("foo")
|
|
| 133 |
+ if err != nil {
|
|
| 134 |
+ t.Errorf("unexpected error: %v", err)
|
|
| 135 |
+ } |
|
| 136 |
+ |
|
| 137 |
+ if route.ID != "foo" {
|
|
| 138 |
+ t.Errorf("Unexpected route: %#v", route)
|
|
| 139 |
+ } |
|
| 140 |
+} |
|
| 141 |
+ |
|
| 142 |
+func TestEtcdGetNotFoundRoutes(t *testing.T) {
|
|
| 143 |
+ fakeClient := tools.NewFakeEtcdClient(t) |
|
| 144 |
+ fakeClient.Data["/routes/foo"] = tools.EtcdResponseWithError{
|
|
| 145 |
+ R: &etcd.Response{
|
|
| 146 |
+ Node: nil, |
|
| 147 |
+ }, |
|
| 148 |
+ E: tools.EtcdErrorNotFound, |
|
| 149 |
+ } |
|
| 150 |
+ registry := NewTestEtcd(fakeClient) |
|
| 151 |
+ route, err := registry.GetRoute("foo")
|
|
| 152 |
+ if err == nil {
|
|
| 153 |
+ t.Errorf("Unexpected non-error.")
|
|
| 154 |
+ } |
|
| 155 |
+ if route != nil {
|
|
| 156 |
+ t.Errorf("Unexpected route: %#v", route)
|
|
| 157 |
+ } |
|
| 158 |
+} |
|
| 159 |
+ |
|
| 160 |
+func TestEtcdCreateRoutes(t *testing.T) {
|
|
| 161 |
+ fakeClient := tools.NewFakeEtcdClient(t) |
|
| 162 |
+ fakeClient.TestIndex = true |
|
| 163 |
+ fakeClient.Data["/routes/foo"] = tools.EtcdResponseWithError{
|
|
| 164 |
+ R: &etcd.Response{
|
|
| 165 |
+ Node: nil, |
|
| 166 |
+ }, |
|
| 167 |
+ E: tools.EtcdErrorNotFound, |
|
| 168 |
+ } |
|
| 169 |
+ registry := NewTestEtcd(fakeClient) |
|
| 170 |
+ err := registry.CreateRoute(&api.Route{
|
|
| 171 |
+ JSONBase: kubeapi.JSONBase{
|
|
| 172 |
+ ID: "foo", |
|
| 173 |
+ }, |
|
| 174 |
+ }) |
|
| 175 |
+ if err != nil {
|
|
| 176 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 177 |
+ } |
|
| 178 |
+ |
|
| 179 |
+ resp, err := fakeClient.Get("/routes/foo", false, false)
|
|
| 180 |
+ if err != nil {
|
|
| 181 |
+ t.Fatalf("Unexpected error %v", err)
|
|
| 182 |
+ } |
|
| 183 |
+ var route api.Route |
|
| 184 |
+ err = latest.Codec.DecodeInto([]byte(resp.Node.Value), &route) |
|
| 185 |
+ if err != nil {
|
|
| 186 |
+ t.Errorf("unexpected error: %v", err)
|
|
| 187 |
+ } |
|
| 188 |
+ |
|
| 189 |
+ if route.ID != "foo" {
|
|
| 190 |
+ t.Errorf("Unexpected route: %#v %s", route, resp.Node.Value)
|
|
| 191 |
+ } |
|
| 192 |
+} |
|
| 193 |
+ |
|
| 194 |
+func TestEtcdCreateAlreadyExistsRoutes(t *testing.T) {
|
|
| 195 |
+ fakeClient := tools.NewFakeEtcdClient(t) |
|
| 196 |
+ fakeClient.Data["/routes/foo"] = tools.EtcdResponseWithError{
|
|
| 197 |
+ R: &etcd.Response{
|
|
| 198 |
+ Node: &etcd.Node{
|
|
| 199 |
+ Value: runtime.EncodeOrDie(latest.Codec, &api.Route{JSONBase: kubeapi.JSONBase{ID: "foo"}}),
|
|
| 200 |
+ }, |
|
| 201 |
+ }, |
|
| 202 |
+ E: nil, |
|
| 203 |
+ } |
|
| 204 |
+ registry := NewTestEtcd(fakeClient) |
|
| 205 |
+ err := registry.CreateRoute(&api.Route{
|
|
| 206 |
+ JSONBase: kubeapi.JSONBase{
|
|
| 207 |
+ ID: "foo", |
|
| 208 |
+ }, |
|
| 209 |
+ }) |
|
| 210 |
+ if err == nil {
|
|
| 211 |
+ t.Error("Unexpected non-error")
|
|
| 212 |
+ } |
|
| 213 |
+ if !errors.IsAlreadyExists(err) {
|
|
| 214 |
+ t.Errorf("Expected 'already exists' error, got %#v", err)
|
|
| 215 |
+ } |
|
| 216 |
+} |
|
| 217 |
+ |
|
| 218 |
+func TestEtcdUpdateOkRoutes(t *testing.T) {
|
|
| 219 |
+ fakeClient := tools.NewFakeEtcdClient(t) |
|
| 220 |
+ registry := NewTestEtcd(fakeClient) |
|
| 221 |
+ err := registry.UpdateRoute(&api.Route{})
|
|
| 222 |
+ if err != nil {
|
|
| 223 |
+ t.Error("Unexpected error")
|
|
| 224 |
+ } |
|
| 225 |
+} |
|
| 226 |
+ |
|
| 227 |
+func TestEtcdDeleteNotFoundRoutes(t *testing.T) {
|
|
| 228 |
+ fakeClient := tools.NewFakeEtcdClient(t) |
|
| 229 |
+ fakeClient.Err = tools.EtcdErrorNotFound |
|
| 230 |
+ registry := NewTestEtcd(fakeClient) |
|
| 231 |
+ err := registry.DeleteRoute("foo")
|
|
| 232 |
+ if err == nil {
|
|
| 233 |
+ t.Error("Unexpected non-error")
|
|
| 234 |
+ } |
|
| 235 |
+ if !errors.IsNotFound(err) {
|
|
| 236 |
+ t.Errorf("Expected 'not found' error, got %#v", err)
|
|
| 237 |
+ } |
|
| 238 |
+} |
|
| 239 |
+ |
|
| 240 |
+func TestEtcdDeleteErrorRoutes(t *testing.T) {
|
|
| 241 |
+ fakeClient := tools.NewFakeEtcdClient(t) |
|
| 242 |
+ fakeClient.Err = fmt.Errorf("Some error")
|
|
| 243 |
+ registry := NewTestEtcd(fakeClient) |
|
| 244 |
+ err := registry.DeleteRoute("foo")
|
|
| 245 |
+ if err == nil {
|
|
| 246 |
+ t.Error("Unexpected non-error")
|
|
| 247 |
+ } |
|
| 248 |
+} |
|
| 249 |
+ |
|
| 250 |
+func TestEtcdDeleteOkRoutes(t *testing.T) {
|
|
| 251 |
+ fakeClient := tools.NewFakeEtcdClient(t) |
|
| 252 |
+ registry := NewTestEtcd(fakeClient) |
|
| 253 |
+ key := "/routes/foo" |
|
| 254 |
+ err := registry.DeleteRoute("foo")
|
|
| 255 |
+ if err != nil {
|
|
| 256 |
+ t.Errorf("Unexpected error: %#v", err)
|
|
| 257 |
+ } |
|
| 258 |
+ if len(fakeClient.DeletedKeys) != 1 {
|
|
| 259 |
+ t.Errorf("Expected 1 delete, found %#v", fakeClient.DeletedKeys)
|
|
| 260 |
+ } else if fakeClient.DeletedKeys[0] != key {
|
|
| 261 |
+ t.Errorf("Unexpected key: %s, expected %s", fakeClient.DeletedKeys[0], key)
|
|
| 262 |
+ } |
|
| 263 |
+} |
| 0 | 264 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,24 @@ |
| 0 |
+package route |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" |
|
| 4 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/openshift/origin/pkg/route/api" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+// Registry is an interface for things that know how to store Routes. |
|
| 10 |
+type Registry interface {
|
|
| 11 |
+ // ListRoutes obtains list of routes that match a selector. |
|
| 12 |
+ ListRoutes(selector labels.Selector) (*api.RouteList, error) |
|
| 13 |
+ // GetRoute retrieves a specific route. |
|
| 14 |
+ GetRoute(routeID string) (*api.Route, error) |
|
| 15 |
+ // CreateRoute creates a new route. |
|
| 16 |
+ CreateRoute(route *api.Route) error |
|
| 17 |
+ // UpdateRoute updates a route. |
|
| 18 |
+ UpdateRoute(route *api.Route) error |
|
| 19 |
+ // DeleteRoute deletes a route. |
|
| 20 |
+ DeleteRoute(routeID string) error |
|
| 21 |
+ // WatchRoutes watches for new/modified/deleted routes. |
|
| 22 |
+ WatchRoutes(labels, fields labels.Selector, resourceVersion uint64) (watch.Interface, error) |
|
| 23 |
+} |
| 0 | 24 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,114 @@ |
| 0 |
+package route |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ "code.google.com/p/go-uuid/uuid" |
|
| 6 |
+ kubeapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 7 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" |
|
| 8 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" |
|
| 9 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" |
|
| 10 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
|
| 11 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
|
| 12 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" |
|
| 13 |
+ |
|
| 14 |
+ "github.com/openshift/origin/pkg/route/api" |
|
| 15 |
+ "github.com/openshift/origin/pkg/route/api/validation" |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+// REST is an implementation of RESTStorage for the api server. |
|
| 19 |
+type REST struct {
|
|
| 20 |
+ registry Registry |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+func NewREST(registry Registry) *REST {
|
|
| 24 |
+ return &REST{
|
|
| 25 |
+ registry: registry, |
|
| 26 |
+ } |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+func (rs *REST) New() runtime.Object {
|
|
| 30 |
+ return &api.Route{}
|
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+// List obtains a list of Routes that match selector. |
|
| 34 |
+func (rs *REST) List(selector, fields labels.Selector) (runtime.Object, error) {
|
|
| 35 |
+ list, err := rs.registry.ListRoutes(selector) |
|
| 36 |
+ if err != nil {
|
|
| 37 |
+ return nil, err |
|
| 38 |
+ } |
|
| 39 |
+ return list, err |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+// Get obtains the route specified by its id. |
|
| 43 |
+func (rs *REST) Get(id string) (runtime.Object, error) {
|
|
| 44 |
+ route, err := rs.registry.GetRoute(id) |
|
| 45 |
+ if err != nil {
|
|
| 46 |
+ return nil, err |
|
| 47 |
+ } |
|
| 48 |
+ return route, err |
|
| 49 |
+} |
|
| 50 |
+ |
|
| 51 |
+// Delete asynchronously deletes the Route specified by its id. |
|
| 52 |
+func (rs *REST) Delete(id string) (<-chan runtime.Object, error) {
|
|
| 53 |
+ _, err := rs.registry.GetRoute(id) |
|
| 54 |
+ if err != nil {
|
|
| 55 |
+ return nil, err |
|
| 56 |
+ } |
|
| 57 |
+ return apiserver.MakeAsync(func() (runtime.Object, error) {
|
|
| 58 |
+ return &kubeapi.Status{Status: kubeapi.StatusSuccess}, rs.registry.DeleteRoute(id)
|
|
| 59 |
+ }), nil |
|
| 60 |
+} |
|
| 61 |
+ |
|
| 62 |
+// Create registers a given new Route instance to rs.registry. |
|
| 63 |
+func (rs *REST) Create(obj runtime.Object) (<-chan runtime.Object, error) {
|
|
| 64 |
+ route, ok := obj.(*api.Route) |
|
| 65 |
+ if !ok {
|
|
| 66 |
+ return nil, fmt.Errorf("not a route: %#v", obj)
|
|
| 67 |
+ } |
|
| 68 |
+ |
|
| 69 |
+ if errs := validation.ValidateRoute(route); len(errs) > 0 {
|
|
| 70 |
+ return nil, errors.NewInvalid("route", route.ID, errs)
|
|
| 71 |
+ } |
|
| 72 |
+ if len(route.ID) == 0 {
|
|
| 73 |
+ route.ID = uuid.NewUUID().String() |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ route.CreationTimestamp = util.Now() |
|
| 77 |
+ |
|
| 78 |
+ return apiserver.MakeAsync(func() (runtime.Object, error) {
|
|
| 79 |
+ err := rs.registry.CreateRoute(route) |
|
| 80 |
+ if err != nil {
|
|
| 81 |
+ return nil, err |
|
| 82 |
+ } |
|
| 83 |
+ return rs.registry.GetRoute(route.ID) |
|
| 84 |
+ }), nil |
|
| 85 |
+} |
|
| 86 |
+ |
|
| 87 |
+// Update replaces a given Route instance with an existing instance in rs.registry. |
|
| 88 |
+func (rs *REST) Update(obj runtime.Object) (<-chan runtime.Object, error) {
|
|
| 89 |
+ route, ok := obj.(*api.Route) |
|
| 90 |
+ if !ok {
|
|
| 91 |
+ return nil, fmt.Errorf("not a route: %#v", obj)
|
|
| 92 |
+ } |
|
| 93 |
+ if len(route.ID) == 0 {
|
|
| 94 |
+ return nil, fmt.Errorf("id is unspecified: %#v", route)
|
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ if errs := validation.ValidateRoute(route); len(errs) > 0 {
|
|
| 98 |
+ return nil, errors.NewInvalid("route", route.ID, errs)
|
|
| 99 |
+ } |
|
| 100 |
+ return apiserver.MakeAsync(func() (runtime.Object, error) {
|
|
| 101 |
+ err := rs.registry.UpdateRoute(route) |
|
| 102 |
+ if err != nil {
|
|
| 103 |
+ return nil, err |
|
| 104 |
+ } |
|
| 105 |
+ return rs.registry.GetRoute(route.ID) |
|
| 106 |
+ }), nil |
|
| 107 |
+} |
|
| 108 |
+ |
|
| 109 |
+// Watch returns Routes events via a watch.Interface. |
|
| 110 |
+// It implements apiserver.ResourceWatcher. |
|
| 111 |
+func (rs *REST) Watch(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
|
|
| 112 |
+ return rs.registry.WatchRoutes(label, field, resourceVersion) |
|
| 113 |
+} |
| 0 | 114 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,279 @@ |
| 0 |
+package route |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "strings" |
|
| 4 |
+ "testing" |
|
| 5 |
+ "time" |
|
| 6 |
+ |
|
| 7 |
+ kubeapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 8 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" |
|
| 9 |
+ "github.com/openshift/origin/pkg/route/api" |
|
| 10 |
+ "github.com/openshift/origin/pkg/route/registry/test" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func TestListRoutesEmptyList(t *testing.T) {
|
|
| 14 |
+ mockRegistry := test.NewRouteRegistry() |
|
| 15 |
+ mockRegistry.Routes = &api.RouteList{
|
|
| 16 |
+ Items: []api.Route{},
|
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 19 |
+ storage := REST{
|
|
| 20 |
+ registry: mockRegistry, |
|
| 21 |
+ } |
|
| 22 |
+ |
|
| 23 |
+ routes, err := storage.List(labels.Everything(), labels.Everything()) |
|
| 24 |
+ if err != nil {
|
|
| 25 |
+ t.Errorf("Unexpected non-nil error: %#v", err)
|
|
| 26 |
+ } |
|
| 27 |
+ |
|
| 28 |
+ if len(routes.(*api.RouteList).Items) != 0 {
|
|
| 29 |
+ t.Errorf("Unexpected non-zero routes list: %#v", routes)
|
|
| 30 |
+ } |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+func TestListRoutesPopulatedList(t *testing.T) {
|
|
| 34 |
+ mockRegistry := test.NewRouteRegistry() |
|
| 35 |
+ mockRegistry.Routes = &api.RouteList{
|
|
| 36 |
+ Items: []api.Route{
|
|
| 37 |
+ {
|
|
| 38 |
+ JSONBase: kubeapi.JSONBase{
|
|
| 39 |
+ ID: "foo", |
|
| 40 |
+ }, |
|
| 41 |
+ }, |
|
| 42 |
+ {
|
|
| 43 |
+ JSONBase: kubeapi.JSONBase{
|
|
| 44 |
+ ID: "bar", |
|
| 45 |
+ }, |
|
| 46 |
+ }, |
|
| 47 |
+ }, |
|
| 48 |
+ } |
|
| 49 |
+ |
|
| 50 |
+ storage := REST{
|
|
| 51 |
+ registry: mockRegistry, |
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ list, err := storage.List(labels.Everything(), labels.Everything()) |
|
| 55 |
+ if err != nil {
|
|
| 56 |
+ t.Errorf("Unexpected non-nil error: %#v", err)
|
|
| 57 |
+ } |
|
| 58 |
+ |
|
| 59 |
+ routes := list.(*api.RouteList) |
|
| 60 |
+ |
|
| 61 |
+ if e, a := 2, len(routes.Items); e != a {
|
|
| 62 |
+ t.Errorf("Expected %v, got %v", e, a)
|
|
| 63 |
+ } |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+func TestCreateRouteBadObject(t *testing.T) {
|
|
| 67 |
+ storage := REST{}
|
|
| 68 |
+ |
|
| 69 |
+ channel, err := storage.Create(&api.RouteList{})
|
|
| 70 |
+ if channel != nil {
|
|
| 71 |
+ t.Errorf("Expected nil, got %v", channel)
|
|
| 72 |
+ } |
|
| 73 |
+ if strings.Index(err.Error(), "not a route") == -1 {
|
|
| 74 |
+ t.Errorf("Expected 'not a route' error, got '%v'", err.Error())
|
|
| 75 |
+ } |
|
| 76 |
+} |
|
| 77 |
+ |
|
| 78 |
+func TestCreateRouteOK(t *testing.T) {
|
|
| 79 |
+ mockRegistry := test.NewRouteRegistry() |
|
| 80 |
+ storage := REST{registry: mockRegistry}
|
|
| 81 |
+ |
|
| 82 |
+ channel, err := storage.Create(&api.Route{
|
|
| 83 |
+ JSONBase: kubeapi.JSONBase{ID: "foo"},
|
|
| 84 |
+ Host: "www.frontend.com", |
|
| 85 |
+ ServiceName: "myrubyservice", |
|
| 86 |
+ }) |
|
| 87 |
+ if channel == nil {
|
|
| 88 |
+ t.Errorf("Expected nil channel, got %v", channel)
|
|
| 89 |
+ } |
|
| 90 |
+ if err != nil {
|
|
| 91 |
+ t.Errorf("Unexpected non-nil error: %#v", err)
|
|
| 92 |
+ } |
|
| 93 |
+ |
|
| 94 |
+ select {
|
|
| 95 |
+ case result := <-channel: |
|
| 96 |
+ route, ok := result.(*api.Route) |
|
| 97 |
+ if !ok {
|
|
| 98 |
+ t.Errorf("Expected route type, got: %#v", result)
|
|
| 99 |
+ } |
|
| 100 |
+ if route.ID != "foo" {
|
|
| 101 |
+ t.Errorf("Unexpected route: %#v", route)
|
|
| 102 |
+ } |
|
| 103 |
+ case <-time.After(50 * time.Millisecond): |
|
| 104 |
+ t.Errorf("Timed out waiting for result")
|
|
| 105 |
+ default: |
|
| 106 |
+ } |
|
| 107 |
+} |
|
| 108 |
+ |
|
| 109 |
+func TestGetRouteError(t *testing.T) {
|
|
| 110 |
+ mockRegistry := test.NewRouteRegistry() |
|
| 111 |
+ storage := REST{registry: mockRegistry}
|
|
| 112 |
+ |
|
| 113 |
+ route, err := storage.Get("foo")
|
|
| 114 |
+ if route != nil {
|
|
| 115 |
+ t.Errorf("Unexpected non-nil route: %#v", route)
|
|
| 116 |
+ } |
|
| 117 |
+ expectedError := "Route foo not found" |
|
| 118 |
+ if err.Error() != expectedError {
|
|
| 119 |
+ t.Errorf("Expected %#v, got %#v", expectedError, err.Error())
|
|
| 120 |
+ } |
|
| 121 |
+} |
|
| 122 |
+ |
|
| 123 |
+func TestGetRouteOK(t *testing.T) {
|
|
| 124 |
+ mockRegistry := test.NewRouteRegistry() |
|
| 125 |
+ mockRegistry.Routes = &api.RouteList{
|
|
| 126 |
+ Items: []api.Route{
|
|
| 127 |
+ {
|
|
| 128 |
+ JSONBase: kubeapi.JSONBase{ID: "foo"},
|
|
| 129 |
+ }, |
|
| 130 |
+ }, |
|
| 131 |
+ } |
|
| 132 |
+ storage := REST{registry: mockRegistry}
|
|
| 133 |
+ |
|
| 134 |
+ route, err := storage.Get("foo")
|
|
| 135 |
+ if route == nil {
|
|
| 136 |
+ t.Error("Unexpected nil route")
|
|
| 137 |
+ } |
|
| 138 |
+ if err != nil {
|
|
| 139 |
+ t.Errorf("Unexpected non-nil error", err)
|
|
| 140 |
+ } |
|
| 141 |
+ if route.(*api.Route).ID != "foo" {
|
|
| 142 |
+ t.Errorf("Unexpected route: %#v", route)
|
|
| 143 |
+ } |
|
| 144 |
+} |
|
| 145 |
+ |
|
| 146 |
+func TestUpdateRouteBadObject(t *testing.T) {
|
|
| 147 |
+ storage := REST{}
|
|
| 148 |
+ |
|
| 149 |
+ channel, err := storage.Update(&api.RouteList{})
|
|
| 150 |
+ if channel != nil {
|
|
| 151 |
+ t.Errorf("Expected nil, got %v", channel)
|
|
| 152 |
+ } |
|
| 153 |
+ if strings.Index(err.Error(), "not a route:") == -1 {
|
|
| 154 |
+ t.Errorf("Expected 'not a route' error, got %v", err)
|
|
| 155 |
+ } |
|
| 156 |
+} |
|
| 157 |
+ |
|
| 158 |
+func TestUpdateRouteMissingID(t *testing.T) {
|
|
| 159 |
+ storage := REST{}
|
|
| 160 |
+ |
|
| 161 |
+ channel, err := storage.Update(&api.Route{})
|
|
| 162 |
+ if channel != nil {
|
|
| 163 |
+ t.Errorf("Expected nil, got %v", channel)
|
|
| 164 |
+ } |
|
| 165 |
+ if strings.Index(err.Error(), "id is unspecified:") == -1 {
|
|
| 166 |
+ t.Errorf("Expected 'id is unspecified' error, got %v", err)
|
|
| 167 |
+ } |
|
| 168 |
+} |
|
| 169 |
+ |
|
| 170 |
+func TestUpdateRegistryErrorSaving(t *testing.T) {
|
|
| 171 |
+ mockRepositoryRegistry := test.NewRouteRegistry() |
|
| 172 |
+ storage := REST{registry: mockRepositoryRegistry}
|
|
| 173 |
+ |
|
| 174 |
+ channel, err := storage.Update(&api.Route{
|
|
| 175 |
+ JSONBase: kubeapi.JSONBase{ID: "foo"},
|
|
| 176 |
+ Host: "www.frontend.com", |
|
| 177 |
+ ServiceName: "rubyservice", |
|
| 178 |
+ }) |
|
| 179 |
+ if err != nil {
|
|
| 180 |
+ t.Errorf("Unexpected non-nil error: %#v", err)
|
|
| 181 |
+ } |
|
| 182 |
+ result := <-channel |
|
| 183 |
+ status, ok := result.(*kubeapi.Status) |
|
| 184 |
+ if !ok {
|
|
| 185 |
+ t.Errorf("Expected status, got %#v", result)
|
|
| 186 |
+ } |
|
| 187 |
+ if status.Status != "failure" || status.Message != "Route foo not found" {
|
|
| 188 |
+ t.Errorf("Expected status=failure, message=Route foo not found, got %#v", status)
|
|
| 189 |
+ } |
|
| 190 |
+} |
|
| 191 |
+ |
|
| 192 |
+func TestUpdateRouteOK(t *testing.T) {
|
|
| 193 |
+ mockRepositoryRegistry := test.NewRouteRegistry() |
|
| 194 |
+ mockRepositoryRegistry.Routes = &api.RouteList{
|
|
| 195 |
+ Items: []api.Route{
|
|
| 196 |
+ {
|
|
| 197 |
+ JSONBase: kubeapi.JSONBase{ID: "bar"},
|
|
| 198 |
+ Host: "www.frontend.com", |
|
| 199 |
+ ServiceName: "rubyservice", |
|
| 200 |
+ }, |
|
| 201 |
+ }, |
|
| 202 |
+ } |
|
| 203 |
+ |
|
| 204 |
+ storage := REST{registry: mockRepositoryRegistry}
|
|
| 205 |
+ |
|
| 206 |
+ channel, err := storage.Update(&api.Route{
|
|
| 207 |
+ JSONBase: kubeapi.JSONBase{ID: "bar"},
|
|
| 208 |
+ Host: "www.newfrontend.com", |
|
| 209 |
+ ServiceName: "newrubyservice", |
|
| 210 |
+ }) |
|
| 211 |
+ |
|
| 212 |
+ if err != nil {
|
|
| 213 |
+ t.Errorf("Unexpected non-nil error: %#v", err)
|
|
| 214 |
+ } |
|
| 215 |
+ result := <-channel |
|
| 216 |
+ route, ok := result.(*api.Route) |
|
| 217 |
+ if !ok {
|
|
| 218 |
+ t.Errorf("Expected Route, got %#v", result)
|
|
| 219 |
+ } |
|
| 220 |
+ if route == nil {
|
|
| 221 |
+ t.Errorf("Nil route returned: %#v", route)
|
|
| 222 |
+ t.Errorf("Expected Route, got %#v", result)
|
|
| 223 |
+ } |
|
| 224 |
+ if route.ID != "bar" {
|
|
| 225 |
+ t.Errorf("Unexpected route returned: %#v", route)
|
|
| 226 |
+ } |
|
| 227 |
+ if route.Host != "www.newfrontend.com" {
|
|
| 228 |
+ t.Errorf("Updated route not returned: %#v", route)
|
|
| 229 |
+ } |
|
| 230 |
+ if route.ServiceName != "newrubyservice" {
|
|
| 231 |
+ t.Errorf("Updated route not returned: %#v", route)
|
|
| 232 |
+ } |
|
| 233 |
+} |
|
| 234 |
+ |
|
| 235 |
+func TestDeleteRouteError(t *testing.T) {
|
|
| 236 |
+ mockRegistry := test.NewRouteRegistry() |
|
| 237 |
+ storage := REST{registry: mockRegistry}
|
|
| 238 |
+ _, err := storage.Delete("foo")
|
|
| 239 |
+ if err == nil {
|
|
| 240 |
+ t.Errorf("Unexpected nil error: %#v", err)
|
|
| 241 |
+ } |
|
| 242 |
+ if err.Error() != "Route foo not found" {
|
|
| 243 |
+ t.Errorf("Expected %#v, got %#v", "Route foo not found", err.Error())
|
|
| 244 |
+ } |
|
| 245 |
+} |
|
| 246 |
+ |
|
| 247 |
+func TestDeleteRouteOk(t *testing.T) {
|
|
| 248 |
+ mockRegistry := test.NewRouteRegistry() |
|
| 249 |
+ mockRegistry.Routes = &api.RouteList{
|
|
| 250 |
+ Items: []api.Route{
|
|
| 251 |
+ {
|
|
| 252 |
+ JSONBase: kubeapi.JSONBase{ID: "foo"},
|
|
| 253 |
+ }, |
|
| 254 |
+ }, |
|
| 255 |
+ } |
|
| 256 |
+ storage := REST{registry: mockRegistry}
|
|
| 257 |
+ channel, err := storage.Delete("foo")
|
|
| 258 |
+ if channel == nil {
|
|
| 259 |
+ t.Error("Unexpected nil channel")
|
|
| 260 |
+ } |
|
| 261 |
+ if err != nil {
|
|
| 262 |
+ t.Errorf("Unexpected non-nil error: %#v", err)
|
|
| 263 |
+ } |
|
| 264 |
+ |
|
| 265 |
+ select {
|
|
| 266 |
+ case result := <-channel: |
|
| 267 |
+ status, ok := result.(*kubeapi.Status) |
|
| 268 |
+ if !ok {
|
|
| 269 |
+ t.Errorf("Expected status type, got: %#v", result)
|
|
| 270 |
+ } |
|
| 271 |
+ if status.Status != "success" {
|
|
| 272 |
+ t.Errorf("Expected status=success, got: %#v", status)
|
|
| 273 |
+ } |
|
| 274 |
+ case <-time.After(50 * time.Millisecond): |
|
| 275 |
+ t.Errorf("Timed out waiting for result")
|
|
| 276 |
+ default: |
|
| 277 |
+ } |
|
| 278 |
+} |
| 0 | 279 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,85 @@ |
| 0 |
+package test |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "errors" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" |
|
| 6 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" |
|
| 7 |
+ routeapi "github.com/openshift/origin/pkg/route/api" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+type RouteRegistry struct {
|
|
| 11 |
+ Routes *routeapi.RouteList |
|
| 12 |
+} |
|
| 13 |
+ |
|
| 14 |
+func NewRouteRegistry() *RouteRegistry {
|
|
| 15 |
+ return &RouteRegistry{}
|
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+func (r *RouteRegistry) ListRoutes(labels labels.Selector) (*routeapi.RouteList, error) {
|
|
| 19 |
+ return r.Routes, nil |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+func (r *RouteRegistry) GetRoute(id string) (*routeapi.Route, error) {
|
|
| 23 |
+ if r.Routes != nil {
|
|
| 24 |
+ for _, route := range r.Routes.Items {
|
|
| 25 |
+ if route.ID == id {
|
|
| 26 |
+ return &route, nil |
|
| 27 |
+ } |
|
| 28 |
+ } |
|
| 29 |
+ } |
|
| 30 |
+ return nil, errors.New("Route " + id + " not found")
|
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+func (r *RouteRegistry) CreateRoute(route *routeapi.Route) error {
|
|
| 34 |
+ if r.Routes == nil {
|
|
| 35 |
+ r.Routes = &routeapi.RouteList{}
|
|
| 36 |
+ } |
|
| 37 |
+ newList := []routeapi.Route{}
|
|
| 38 |
+ for _, curRoute := range r.Routes.Items {
|
|
| 39 |
+ newList = append(newList, curRoute) |
|
| 40 |
+ } |
|
| 41 |
+ newList = append(newList, *route) |
|
| 42 |
+ r.Routes.Items = newList |
|
| 43 |
+ return nil |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+func (r *RouteRegistry) UpdateRoute(route *routeapi.Route) error {
|
|
| 47 |
+ if r.Routes == nil {
|
|
| 48 |
+ r.Routes = &routeapi.RouteList{}
|
|
| 49 |
+ } |
|
| 50 |
+ newList := []routeapi.Route{}
|
|
| 51 |
+ found := false |
|
| 52 |
+ for _, curRoute := range r.Routes.Items {
|
|
| 53 |
+ if curRoute.ID == route.ID {
|
|
| 54 |
+ // route to be updated exists |
|
| 55 |
+ found = true |
|
| 56 |
+ } else {
|
|
| 57 |
+ newList = append(newList, curRoute) |
|
| 58 |
+ } |
|
| 59 |
+ } |
|
| 60 |
+ if !found {
|
|
| 61 |
+ return errors.New("Route " + route.ID + " not found")
|
|
| 62 |
+ } |
|
| 63 |
+ newList = append(newList, *route) |
|
| 64 |
+ r.Routes.Items = newList |
|
| 65 |
+ return nil |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+func (r *RouteRegistry) DeleteRoute(id string) error {
|
|
| 69 |
+ if r.Routes == nil {
|
|
| 70 |
+ r.Routes = &routeapi.RouteList{}
|
|
| 71 |
+ } |
|
| 72 |
+ newList := []routeapi.Route{}
|
|
| 73 |
+ for _, curRoute := range r.Routes.Items {
|
|
| 74 |
+ if curRoute.ID != id {
|
|
| 75 |
+ newList = append(newList, curRoute) |
|
| 76 |
+ } |
|
| 77 |
+ } |
|
| 78 |
+ r.Routes.Items = newList |
|
| 79 |
+ return nil |
|
| 80 |
+} |
|
| 81 |
+ |
|
| 82 |
+func (r *RouteRegistry) WatchRoutes(labels, fields labels.Selector, resourceVersion uint64) (watch.Interface, error) {
|
|
| 83 |
+ return nil, nil |
|
| 84 |
+} |