Commit fdc16739 authored by jackgr's avatar jackgr

First set of changes for persistent repository.

parent e1a76a40
...@@ -35,7 +35,6 @@ type Schema struct { ...@@ -35,7 +35,6 @@ type Schema struct {
// the creation, modification and/or deletion of a set of resources. // the creation, modification and/or deletion of a set of resources.
type Deployment struct { type Deployment struct {
Name string `json:"name"` Name string `json:"name"`
ID int `json:"id"`
CreatedAt time.Time `json:"createdAt,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty"`
DeployedAt time.Time `json:"deployedAt,omitempty"` DeployedAt time.Time `json:"deployedAt,omitempty"`
ModifiedAt time.Time `json:"modifiedAt,omitempty"` ModifiedAt time.Time `json:"modifiedAt,omitempty"`
......
######################################################################
# Copyright 2015 The Kubernetes Authors All rights reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
######################################################################
---
apiVersion: v1
kind: Namespace
metadata:
labels:
app: dm
name: dm-namespace
name: dm
---
apiVersion: v1
kind: Service
metadata:
labels:
app: dm
name: expandybird-service
name: expandybird-service
namespace: dm
spec:
ports:
- name: expandybird
port: 8081
targetPort: 8080
selector:
app: dm
name: expandybird
---
apiVersion: v1
kind: ReplicationController
metadata:
labels:
app: dm
name: expandybird-rc
name: expandybird-rc
namespace: dm
spec:
replicas: 2
selector:
app: dm
name: expandybird
template:
metadata:
labels:
app: dm
name: expandybird
spec:
containers:
- env: []
image: gcr.io/dm-k8s-test-jackgr/expandybird:latest
name: expandybird
ports:
- containerPort: 8080
name: expandybird
---
apiVersion: v1
kind: Service
metadata:
labels:
app: dm
name: resourcifier-service
name: resourcifier-service
namespace: dm
spec:
ports:
- name: resourcifier
port: 8082
targetPort: 8080
selector:
app: dm
name: resourcifier
---
apiVersion: v1
kind: ReplicationController
metadata:
labels:
app: dm
name: resourcifier-rc
name: resourcifier-rc
namespace: dm
spec:
replicas: 2
selector:
app: dm
name: resourcifier
template:
metadata:
labels:
app: dm
name: resourcifier
spec:
containers:
- env: []
image: gcr.io/dm-k8s-test-jackgr/resourcifier:latest
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
timeoutSeconds: 1
name: resourcifier
ports:
- containerPort: 8080
name: resourcifier
---
apiVersion: v1
kind: Service
metadata:
labels:
app: dm
name: manager-service
name: manager-service
namespace: dm
spec:
ports:
- name: manager
port: 8080
targetPort: 8080
selector:
app: dm
name: manager
---
apiVersion: v1
kind: ReplicationController
metadata:
labels:
app: dm
name: manager-rc
name: manager-rc
namespace: dm
spec:
replicas: 1
selector:
app: dm
name: manager
template:
metadata:
labels:
app: dm
name: manager
spec:
containers:
- env: []
image: gcr.io/dm-k8s-test-jackgr/manager:latest
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
timeoutSeconds: 1
name: manager
ports:
- containerPort: 8080
name: manager
...@@ -36,6 +36,8 @@ import ( ...@@ -36,6 +36,8 @@ import (
"github.com/kubernetes/deployment-manager/common" "github.com/kubernetes/deployment-manager/common"
"github.com/kubernetes/deployment-manager/manager/manager" "github.com/kubernetes/deployment-manager/manager/manager"
"github.com/kubernetes/deployment-manager/manager/repository" "github.com/kubernetes/deployment-manager/manager/repository"
"github.com/kubernetes/deployment-manager/manager/repository/persistent"
"github.com/kubernetes/deployment-manager/manager/repository/transient"
"github.com/kubernetes/deployment-manager/registry" "github.com/kubernetes/deployment-manager/registry"
"github.com/kubernetes/deployment-manager/util" "github.com/kubernetes/deployment-manager/util"
) )
...@@ -69,6 +71,9 @@ var ( ...@@ -69,6 +71,9 @@ var (
deployerURL = flag.String("deployerURL", "", "The URL for the deployer service.") deployerURL = flag.String("deployerURL", "", "The URL for the deployer service.")
credentialFile = flag.String("credentialFile", "", "Local file to use for credentials.") credentialFile = flag.String("credentialFile", "", "Local file to use for credentials.")
credentialSecrets = flag.Bool("credentialSecrets", true, "Use secrets for credentials.") credentialSecrets = flag.Bool("credentialSecrets", true, "Use secrets for credentials.")
mongoName = flag.String("mongoName", "mongodb", "The DNS name of the mongodb service.")
mongoPort = flag.String("mongoPort", "27017", "The port of the mongodb service.")
mongoAddress = flag.String("mongoAddress", "mongodb:27017", "The address of the mongodb service.")
) )
var backend manager.Manager var backend manager.Manager
...@@ -97,27 +102,39 @@ func init() { ...@@ -97,27 +102,39 @@ func init() {
backend = newManager(credentialProvider) backend = newManager(credentialProvider)
} }
const expanderPort = "8080"
const deployerPort = "8080"
func newManager(cp common.CredentialProvider) manager.Manager { func newManager(cp common.CredentialProvider) manager.Manager {
service := registry.NewInmemRegistryService() service := registry.NewInmemRegistryService()
registryProvider := registry.NewDefaultRegistryProvider(cp, service) registryProvider := registry.NewDefaultRegistryProvider(cp, service)
resolver := manager.NewTypeResolver(registryProvider, util.DefaultHTTPClient()) resolver := manager.NewTypeResolver(registryProvider, util.DefaultHTTPClient())
expander := manager.NewExpander(getServiceURL(*expanderURL, *expanderName), resolver) expander := manager.NewExpander(getServiceURL(*expanderURL, *expanderName, expanderPort), resolver)
deployer := manager.NewDeployer(getServiceURL(*deployerURL, *deployerName)) deployer := manager.NewDeployer(getServiceURL(*deployerURL, *deployerName, deployerPort))
r := repository.NewMapBasedRepository() address := strings.TrimPrefix(getServiceURL(*mongoAddress, *mongoName, *mongoPort), "https://")
credentialProvider := cp repository := createRepository(address)
return manager.NewManager(expander, deployer, r, registryProvider, service, credentialProvider) return manager.NewManager(expander, deployer, repository, registryProvider, service, cp)
}
func createRepository(address string) repository.Repository {
r, err := persistent.NewRepository(address)
if err != nil {
r = transient.NewRepository()
}
return r
} }
func getServiceURL(serviceURL, serviceName string) string { func getServiceURL(serviceURL, serviceName, servicePort string) string {
if serviceURL == "" { if serviceURL == "" {
serviceURL = makeEnvVariableURL(serviceName) serviceURL = makeEnvVariableURL(serviceName)
if serviceURL == "" { if serviceURL == "" {
addrs, err := net.LookupHost(serviceName) addrs, err := net.LookupHost(serviceName)
if err != nil || len(addrs) < 1 { if err != nil || len(addrs) < 1 {
log.Fatalf("cannot resolve service:%v. environment:%v", serviceName, os.Environ()) log.Fatalf("cannot resolve service:%v. environment:%v\n", serviceName, os.Environ())
} }
serviceURL = fmt.Sprintf("https://%s", addrs[0]) serviceURL = fmt.Sprintf("https://%s:%s", addrs[0], servicePort)
} }
} }
...@@ -130,7 +147,7 @@ func getServiceURL(serviceURL, serviceName string) string { ...@@ -130,7 +147,7 @@ func getServiceURL(serviceURL, serviceName string) string {
func makeEnvVariableURL(str string) string { func makeEnvVariableURL(str string) string {
prefix := makeEnvVariableName(str) prefix := makeEnvVariableName(str)
url := os.Getenv(prefix + "_PORT") url := os.Getenv(prefix + "_PORT")
return strings.Replace(url, "tcp", "http", 1) return strings.Replace(url, "tcp", "https", 1)
} }
// makeEnvVariableName is copied from the Kubernetes source, // makeEnvVariableName is copied from the Kubernetes source,
...@@ -335,7 +352,13 @@ func expandHandlerFunc(w http.ResponseWriter, r *http.Request) { ...@@ -335,7 +352,13 @@ func expandHandlerFunc(w http.ResponseWriter, r *http.Request) {
func listTypesHandlerFunc(w http.ResponseWriter, r *http.Request) { func listTypesHandlerFunc(w http.ResponseWriter, r *http.Request) {
handler := "manager: list types" handler := "manager: list types"
util.LogHandlerEntry(handler, r) util.LogHandlerEntry(handler, r)
util.LogHandlerExitWithJSON(handler, w, backend.ListTypes(), http.StatusOK) types, err := backend.ListTypes()
if err != nil {
util.LogAndReturnError(handler, http.StatusBadRequest, err, w)
return
}
util.LogHandlerExitWithJSON(handler, w, types, http.StatusOK)
} }
func listTypeInstancesHandlerFunc(w http.ResponseWriter, r *http.Request) { func listTypeInstancesHandlerFunc(w http.ResponseWriter, r *http.Request) {
...@@ -346,7 +369,13 @@ func listTypeInstancesHandlerFunc(w http.ResponseWriter, r *http.Request) { ...@@ -346,7 +369,13 @@ func listTypeInstancesHandlerFunc(w http.ResponseWriter, r *http.Request) {
return return
} }
util.LogHandlerExitWithJSON(handler, w, backend.ListInstances(typeName), http.StatusOK) instances, err := backend.ListInstances(typeName)
if err != nil {
util.LogAndReturnError(handler, http.StatusBadRequest, err, w)
return
}
util.LogHandlerExitWithJSON(handler, w, instances, http.StatusOK)
} }
// Putting Registry handlers here for now because deployments.go // Putting Registry handlers here for now because deployments.go
......
...@@ -21,11 +21,11 @@ import ( ...@@ -21,11 +21,11 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"time"
"log" "log"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"time"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/kubernetes/deployment-manager/common" "github.com/kubernetes/deployment-manager/common"
......
...@@ -44,8 +44,8 @@ type Manager interface { ...@@ -44,8 +44,8 @@ type Manager interface {
Expand(t *common.Template) (*common.Manifest, error) Expand(t *common.Template) (*common.Manifest, error)
// Types // Types
ListTypes() []string ListTypes() ([]string, error)
ListInstances(typeName string) []*common.TypeInstance ListInstances(typeName string) ([]*common.TypeInstance, error)
// Registries // Registries
ListRegistries() ([]*common.Registry, error) ListRegistries() ([]*common.Registry, error)
...@@ -141,7 +141,7 @@ func (m *manager) CreateDeployment(t *common.Template) (*common.Deployment, erro ...@@ -141,7 +141,7 @@ func (m *manager) CreateDeployment(t *common.Template) (*common.Deployment, erro
return nil, err return nil, err
} }
if err := m.repository.AddManifest(t.Name, manifest); err != nil { if err := m.repository.AddManifest(manifest); err != nil {
log.Printf("AddManifest failed %v", err) log.Printf("AddManifest failed %v", err)
m.repository.SetDeploymentState(t.Name, failState(err)) m.repository.SetDeploymentState(t.Name, failState(err))
return nil, err return nil, err
...@@ -173,14 +173,14 @@ func (m *manager) CreateDeployment(t *common.Template) (*common.Deployment, erro ...@@ -173,14 +173,14 @@ func (m *manager) CreateDeployment(t *common.Template) (*common.Deployment, erro
// Update the manifest with the actual state of the reified resources // Update the manifest with the actual state of the reified resources
manifest.ExpandedConfig = actualConfig manifest.ExpandedConfig = actualConfig
if err := m.repository.SetManifest(t.Name, manifest); err != nil { if err := m.repository.SetManifest(manifest); err != nil {
log.Printf("SetManifest failed %v", err) log.Printf("SetManifest failed %v", err)
m.repository.SetDeploymentState(t.Name, failState(err)) m.repository.SetDeploymentState(t.Name, failState(err))
return nil, err return nil, err
} }
// Finally update the type instances for this deployment. // Finally update the type instances for this deployment.
m.addTypeInstances(t.Name, manifest.Name, manifest.Layout) m.setTypeInstances(t.Name, manifest.Name, manifest.Layout)
return m.repository.GetValidDeployment(t.Name) return m.repository.GetValidDeployment(t.Name)
} }
...@@ -200,15 +200,15 @@ func (m *manager) createManifest(t *common.Template) (*common.Manifest, error) { ...@@ -200,15 +200,15 @@ func (m *manager) createManifest(t *common.Template) (*common.Manifest, error) {
}, nil }, nil
} }
func (m *manager) addTypeInstances(deploymentName string, manifestName string, layout *common.Layout) { func (m *manager) setTypeInstances(deploymentName string, manifestName string, layout *common.Layout) {
m.repository.ClearTypeInstances(deploymentName) m.repository.ClearTypeInstancesForDeployment(deploymentName)
instances := make(map[string][]*common.TypeInstance) instances := make(map[string][]*common.TypeInstance)
for i, r := range layout.Resources { for i, r := range layout.Resources {
addTypeInstances(&instances, r, deploymentName, manifestName, fmt.Sprintf("$.resources[%d]", i)) addTypeInstances(&instances, r, deploymentName, manifestName, fmt.Sprintf("$.resources[%d]", i))
} }
m.repository.SetTypeInstances(deploymentName, instances) m.repository.AddTypeInstances(instances)
} }
func addTypeInstances(instances *map[string][]*common.TypeInstance, r *common.LayoutResource, deploymentName string, manifestName string, jsonPath string) { func addTypeInstances(instances *map[string][]*common.TypeInstance, r *common.LayoutResource, deploymentName string, manifestName string, jsonPath string) {
...@@ -220,6 +220,7 @@ func addTypeInstances(instances *map[string][]*common.TypeInstance, r *common.La ...@@ -220,6 +220,7 @@ func addTypeInstances(instances *map[string][]*common.TypeInstance, r *common.La
Manifest: manifestName, Manifest: manifestName,
Path: jsonPath, Path: jsonPath,
} }
(*instances)[r.Type] = append((*instances)[r.Type], inst) (*instances)[r.Type] = append((*instances)[r.Type], inst)
// Add all sub resources if they exist. // Add all sub resources if they exist.
...@@ -260,13 +261,14 @@ func (m *manager) DeleteDeployment(name string, forget bool) (*common.Deployment ...@@ -260,13 +261,14 @@ func (m *manager) DeleteDeployment(name string, forget bool) (*common.Deployment
} }
// Create an empty manifest since resources have been deleted. // Create an empty manifest since resources have been deleted.
err = m.repository.AddManifest(name, &common.Manifest{Deployment: name, Name: generateManifestName()}) if !forget {
if err != nil { manifest := &common.Manifest{Deployment: name, Name: generateManifestName()}
if err := m.repository.AddManifest(manifest); err != nil {
log.Printf("Failed to add empty manifest") log.Printf("Failed to add empty manifest")
m.repository.SetDeploymentState(name, failState(err))
return nil, err return nil, err
} }
} }
}
d, err = m.repository.DeleteDeployment(name, forget) d, err = m.repository.DeleteDeployment(name, forget)
if err != nil { if err != nil {
...@@ -274,8 +276,7 @@ func (m *manager) DeleteDeployment(name string, forget bool) (*common.Deployment ...@@ -274,8 +276,7 @@ func (m *manager) DeleteDeployment(name string, forget bool) (*common.Deployment
} }
// Finally remove the type instances for this deployment. // Finally remove the type instances for this deployment.
m.repository.ClearTypeInstances(name) m.repository.ClearTypeInstancesForDeployment(name)
return d, nil return d, nil
} }
...@@ -301,15 +302,14 @@ func (m *manager) PutDeployment(name string, t *common.Template) (*common.Deploy ...@@ -301,15 +302,14 @@ func (m *manager) PutDeployment(name string, t *common.Template) (*common.Deploy
} }
manifest.ExpandedConfig = actualConfig manifest.ExpandedConfig = actualConfig
err = m.repository.AddManifest(t.Name, manifest) err = m.repository.AddManifest(manifest)
if err != nil { if err != nil {
m.repository.SetDeploymentState(name, failState(err)) m.repository.SetDeploymentState(name, failState(err))
return nil, err return nil, err
} }
// Finally update the type instances for this deployment. // Finally update the type instances for this deployment.
m.addTypeInstances(t.Name, manifest.Name, manifest.Layout) m.setTypeInstances(t.Name, manifest.Name, manifest.Layout)
return m.repository.GetValidDeployment(t.Name) return m.repository.GetValidDeployment(t.Name)
} }
...@@ -326,11 +326,11 @@ func (m *manager) Expand(t *common.Template) (*common.Manifest, error) { ...@@ -326,11 +326,11 @@ func (m *manager) Expand(t *common.Template) (*common.Manifest, error) {
}, nil }, nil
} }
func (m *manager) ListTypes() []string { func (m *manager) ListTypes() ([]string, error) {
return m.repository.ListTypes() return m.repository.ListTypes()
} }
func (m *manager) ListInstances(typeName string) []*common.TypeInstance { func (m *manager) ListInstances(typeName string) ([]*common.TypeInstance, error) {
return m.repository.GetTypeInstances(typeName) return m.repository.GetTypeInstances(typeName)
} }
......
...@@ -160,6 +160,7 @@ func newRepositoryStub() *repositoryStub { ...@@ -160,6 +160,7 @@ func newRepositoryStub() *repositoryStub {
return ret return ret
} }
// Deployments.
func (repository *repositoryStub) ListDeployments() ([]common.Deployment, error) { func (repository *repositoryStub) ListDeployments() ([]common.Deployment, error) {
if repository.FailListDeployments { if repository.FailListDeployments {
return deploymentList, errTest return deploymentList, errTest
...@@ -181,11 +182,6 @@ func (repository *repositoryStub) GetValidDeployment(d string) (*common.Deployme ...@@ -181,11 +182,6 @@ func (repository *repositoryStub) GetValidDeployment(d string) (*common.Deployme
return &deployment, nil return &deployment, nil
} }
func (repository *repositoryStub) SetDeploymentState(name string, state *common.DeploymentState) error {
repository.DeploymentStates = append(repository.DeploymentStates, state)
return nil
}
func (repository *repositoryStub) CreateDeployment(d string) (*common.Deployment, error) { func (repository *repositoryStub) CreateDeployment(d string) (*common.Deployment, error) {
repository.Created = append(repository.Created, d) repository.Created = append(repository.Created, d)
return &deployment, nil return &deployment, nil
...@@ -196,22 +192,20 @@ func (repository *repositoryStub) DeleteDeployment(d string, forget bool) (*comm ...@@ -196,22 +192,20 @@ func (repository *repositoryStub) DeleteDeployment(d string, forget bool) (*comm
return &deployment, nil return &deployment, nil
} }
func (repository *repositoryStub) AddManifest(d string, manifest *common.Manifest) error { func (repository *repositoryStub) SetDeploymentState(name string, state *common.DeploymentState) error {
repository.ManifestAdd[d] = manifest repository.DeploymentStates = append(repository.DeploymentStates, state)
return nil return nil
} }
func (repository *repositoryStub) SetManifest(d string, manifest *common.Manifest) error { // Manifests.
repository.ManifestSet[d] = manifest func (repository *repositoryStub) AddManifest(manifest *common.Manifest) error {
repository.ManifestAdd[manifest.Deployment] = manifest
return nil return nil
} }
func (repository *repositoryStub) GetLatestManifest(d string) (*common.Manifest, error) { func (repository *repositoryStub) SetManifest(manifest *common.Manifest) error {
if d == deploymentName { repository.ManifestSet[manifest.Deployment] = manifest
return repository.ManifestAdd[d], nil return nil
}
return nil, errTest
} }
func (repository *repositoryStub) ListManifests(d string) (map[string]*common.Manifest, error) { func (repository *repositoryStub) ListManifests(d string) (map[string]*common.Manifest, error) {
...@@ -230,26 +224,43 @@ func (repository *repositoryStub) GetManifest(d string, m string) (*common.Manif ...@@ -230,26 +224,43 @@ func (repository *repositoryStub) GetManifest(d string, m string) (*common.Manif
return nil, errTest return nil, errTest
} }
func (repository *repositoryStub) ListTypes() []string { func (repository *repositoryStub) GetLatestManifest(d string) (*common.Manifest, error) {
if d == deploymentName {
return repository.ManifestAdd[d], nil
}
return nil, errTest
}
// Types.
func (repository *repositoryStub) ListTypes() ([]string, error) {
repository.ListTypesCalled = true repository.ListTypesCalled = true
return []string{} return []string{}, nil
} }
func (repository *repositoryStub) GetTypeInstances(t string) []*common.TypeInstance { func (repository *repositoryStub) GetTypeInstances(t string) ([]*common.TypeInstance, error) {
repository.GetTypeInstancesCalled = true repository.GetTypeInstancesCalled = true
return []*common.TypeInstance{} return []*common.TypeInstance{}, nil
} }
func (repository *repositoryStub) ClearTypeInstances(d string) { func (repository *repositoryStub) ClearTypeInstancesForDeployment(d string) error {
repository.TypeInstancesCleared = true repository.TypeInstancesCleared = true
return nil
} }
func (repository *repositoryStub) SetTypeInstances(d string, is map[string][]*common.TypeInstance) { func (repository *repositoryStub) AddTypeInstances(is map[string][]*common.TypeInstance) error {
for k := range is { for t, instances := range is {
repository.TypeInstances[d] = append(repository.TypeInstances[d], k) for _, instance := range instances {
d := instance.Deployment
repository.TypeInstances[d] = append(repository.TypeInstances[d], t)
}
} }
return nil
} }
func (repository *repositoryStub) Close() {}
var testExpander = &expanderStub{} var testExpander = &expanderStub{}
var testRepository = newRepositoryStub() var testRepository = newRepositoryStub()
var testDeployer = newDeployerStub() var testDeployer = newDeployerStub()
......
This diff is collapsed.
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package persistent
import (
"github.com/kubernetes/deployment-manager/manager/repository"
"sync"
"testing"
)
var tryRepository = true
var repositoryLock sync.RWMutex
func createRepository() repository.Repository {
repositoryLock.Lock()
defer repositoryLock.Unlock()
if tryRepository {
r, err := NewRepository("mongodb://localhost")
if err == nil {
return r
}
}
tryRepository = false
return nil
}
func resetRepository(t *testing.T, r repository.Repository) {
if r != nil {
if err := r.(*pRepository).Reset(); err != nil {
t.Fatalf("cannot reset repository: %s\n", err)
}
}
}
func TestRepositoryListEmpty(t *testing.T) {
if r := createRepository(); r != nil {
defer resetRepository(t, r)
repository.TestRepositoryListEmpty(t, r)
}
}
func TestRepositoryGetFailsWithNonExistentDeployment(t *testing.T) {
if r := createRepository(); r != nil {
defer resetRepository(t, r)
repository.TestRepositoryGetFailsWithNonExistentDeployment(t, r)
}
}
func TestRepositoryCreateDeploymentWorks(t *testing.T) {
if r := createRepository(); r != nil {
defer resetRepository(t, r)
repository.TestRepositoryCreateDeploymentWorks(t, r)
}
}
func TestRepositoryMultipleManifestsWorks(t *testing.T) {
if r := createRepository(); r != nil {
defer resetRepository(t, r)
repository.TestRepositoryMultipleManifestsWorks(t, r)
}
}
func TestRepositoryDeleteFailsWithNonExistentDeployment(t *testing.T) {
if r := createRepository(); r != nil {
defer resetRepository(t, r)
repository.TestRepositoryDeleteFailsWithNonExistentDeployment(t, r)
}
}
func TestRepositoryDeleteWorksWithNoLatestManifest(t *testing.T) {
if r := createRepository(); r != nil {
defer resetRepository(t, r)
repository.TestRepositoryDeleteWorksWithNoLatestManifest(t, r)
}
}
func TestRepositoryDeleteDeploymentWorksNoForget(t *testing.T) {
if r := createRepository(); r != nil {
defer resetRepository(t, r)
repository.TestRepositoryDeleteDeploymentWorksNoForget(t, r)
}
}
func TestRepositoryDeleteDeploymentWorksForget(t *testing.T) {
if r := createRepository(); r != nil {
defer resetRepository(t, r)
repository.TestRepositoryDeleteDeploymentWorksForget(t, r)
}
}
func TestRepositoryTypeInstances(t *testing.T) {
if r := createRepository(); r != nil {
defer resetRepository(t, r)
repository.TestRepositoryTypeInstances(t, r)
}
}
...@@ -14,17 +14,10 @@ See the License for the specific language governing permissions and ...@@ -14,17 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
// Package repository implements a deployment repository using a map. // Package repository defines a deployment repository.
// It can be easily replaced by a deployment repository that uses some
// form of persistent storage.
package repository package repository
import ( import (
"fmt"
"log"
"sync"
"time"
"github.com/kubernetes/deployment-manager/common" "github.com/kubernetes/deployment-manager/common"
) )
...@@ -40,319 +33,17 @@ type Repository interface { ...@@ -40,319 +33,17 @@ type Repository interface {
SetDeploymentState(name string, state *common.DeploymentState) error SetDeploymentState(name string, state *common.DeploymentState) error
// Manifests. // Manifests.
AddManifest(deploymentName string, manifest *common.Manifest) error AddManifest(manifest *common.Manifest) error
SetManifest(deploymentName string, manifest *common.Manifest) error SetManifest(manifest *common.Manifest) error
ListManifests(deploymentName string) (map[string]*common.Manifest, error) ListManifests(deploymentName string) (map[string]*common.Manifest, error)
GetManifest(deploymentName string, manifestName string) (*common.Manifest, error) GetManifest(deploymentName string, manifestName string) (*common.Manifest, error)
GetLatestManifest(deploymentName string) (*common.Manifest, error) GetLatestManifest(deploymentName string) (*common.Manifest, error)
// Types. // Types.
ListTypes() []string ListTypes() ([]string, error)
GetTypeInstances(typeName string) []*common.TypeInstance GetTypeInstances(typeName string) ([]*common.TypeInstance, error)
ClearTypeInstances(deploymentName string) ClearTypeInstancesForDeployment(deploymentName string) error
SetTypeInstances(deploymentName string, instances map[string][]*common.TypeInstance) AddTypeInstances(instances map[string][]*common.TypeInstance) error
}
// deploymentTypeInstanceMap stores type instances mapped by deployment name.
// This allows for simple updating and deleting of per-deployment instances
// when deployments are created/updated/deleted.
type deploymentTypeInstanceMap map[string][]*common.TypeInstance
type typeInstanceMap map[string]deploymentTypeInstanceMap
type mapBasedRepository struct {
sync.RWMutex
deployments map[string]common.Deployment
manifests map[string]map[string]*common.Manifest
instances typeInstanceMap
}
// NewMapBasedRepository returns a new map based repository.
func NewMapBasedRepository() Repository {
return &mapBasedRepository{
deployments: make(map[string]common.Deployment, 0),
manifests: make(map[string]map[string]*common.Manifest, 0),
instances: typeInstanceMap{},
}
}
// ListDeployments returns of all of the deployments in the repository.
func (r *mapBasedRepository) ListDeployments() ([]common.Deployment, error) {
r.RLock()
defer r.RUnlock()
l := []common.Deployment{}
for _, deployment := range r.deployments {
l = append(l, deployment)
}
return l, nil
}
// GetDeployment returns the deployment with the supplied name.
// If the deployment is not found, it returns an error.
func (r *mapBasedRepository) GetDeployment(name string) (*common.Deployment, error) {
d, ok := r.deployments[name]
if !ok {
return nil, fmt.Errorf("deployment %s not found", name)
}
return &d, nil
}
// GetValidDeployment returns the deployment with the supplied name.
// If the deployment is not found or marked as deleted, it returns an error.
func (r *mapBasedRepository) GetValidDeployment(name string) (*common.Deployment, error) {
d, err := r.GetDeployment(name)
if err != nil {
return nil, err
}
if d.State.Status == common.DeletedStatus {
return nil, fmt.Errorf("deployment %s is deleted", name)
}
return d, nil
}
// SetDeploymentState sets the DeploymentState of the deployment and updates ModifiedAt
func (r *mapBasedRepository) SetDeploymentState(name string, state *common.DeploymentState) error {
return func() error {
r.Lock()
defer r.Unlock()
d, err := r.GetValidDeployment(name)
if err != nil {
return err
}
d.State = state
d.ModifiedAt = time.Now()
r.deployments[name] = *d
return nil
}()
}
// CreateDeployment creates a new deployment and stores it in the repository.
func (r *mapBasedRepository) CreateDeployment(name string) (*common.Deployment, error) {
d, err := func() (*common.Deployment, error) {
r.Lock()
defer r.Unlock()
exists, _ := r.GetValidDeployment(name)
if exists != nil {
return nil, fmt.Errorf("Deployment %s already exists", name)
}
d := common.NewDeployment(name)
d.DeployedAt = time.Now()
r.deployments[name] = *d
return d, nil
}()
if err != nil {
return nil, err
}
log.Printf("created deployment: %v", d)
return d, nil
}
// AddManifest adds a manifest to the repository and repoints the latest
// manifest to it for the corresponding deployment.
func (r *mapBasedRepository) AddManifest(deploymentName string, manifest *common.Manifest) error {
r.Lock()
defer r.Unlock()
l, err := r.listManifestsForDeployment(deploymentName)
if err != nil {
return err
}
// Make sure the manifest doesn't already exist, and if not, add the manifest to
// map of manifests this deployment has
if _, ok := l[manifest.Name]; ok {
return fmt.Errorf("Manifest %s already exists in deployment %s", manifest.Name, deploymentName)
}
d, err := r.GetValidDeployment(deploymentName)
if err != nil {
return err
}
l[manifest.Name] = manifest
d.LatestManifest = manifest.Name
r.deployments[deploymentName] = *d
log.Printf("Added manifest %s to deployment: %s", manifest.Name, deploymentName)
return nil
}
// SetManifest sets an existing manifest in the repository to provided
// manifest.
func (r *mapBasedRepository) SetManifest(deploymentName string, manifest *common.Manifest) error {
r.Lock()
defer r.Unlock()
l, err := r.listManifestsForDeployment(deploymentName)
if err != nil {
return err
}
l[manifest.Name] = manifest
return nil
}
// DeleteDeployment deletes the deployment with the supplied name.
// If forget is true, then the deployment is removed from the repository.
// Otherwise, it is marked as deleted and retained.
func (r *mapBasedRepository) DeleteDeployment(name string, forget bool) (*common.Deployment, error) {
d, err := func() (*common.Deployment, error) {
r.Lock()
defer r.Unlock()
d, err := r.GetValidDeployment(name)
if err != nil {
return nil, err
}
if !forget {
d.DeletedAt = time.Now()
d.State = &common.DeploymentState{Status: common.DeletedStatus}
r.deployments[name] = *d
} else {
delete(r.deployments, name)
delete(r.manifests, name)
d.LatestManifest = ""
}
return d, nil
}()
if err != nil {
return nil, err
}
log.Printf("deleted deployment: %v", d)
return d, nil
}
func (r *mapBasedRepository) ListManifests(deploymentName string) (map[string]*common.Manifest, error) {
r.Lock()
defer r.Unlock()
_, err := r.GetValidDeployment(deploymentName)
if err != nil {
return nil, err
}
return r.listManifestsForDeployment(deploymentName)
}
func (r *mapBasedRepository) listManifestsForDeployment(deploymentName string) (map[string]*common.Manifest, error) {
l, ok := r.manifests[deploymentName]
if !ok {
l = make(map[string]*common.Manifest, 0)
r.manifests[deploymentName] = l
}
return l, nil
}
func (r *mapBasedRepository) GetManifest(deploymentName string, manifestName string) (*common.Manifest, error) {
r.Lock()
defer r.Unlock()
_, err := r.GetValidDeployment(deploymentName)
if err != nil {
return nil, err
}
return r.getManifestForDeployment(deploymentName, manifestName)
}
func (r *mapBasedRepository) getManifestForDeployment(deploymentName string, manifestName string) (*common.Manifest, error) {
l, err := r.listManifestsForDeployment(deploymentName)
if err != nil {
return nil, err
}
m, ok := l[manifestName]
if !ok {
return nil, fmt.Errorf("manifest %s not found in deployment %s", manifestName, deploymentName)
}
return m, nil
}
// GetLatestManifest returns the latest manifest for a given deployment,
// which by definition is the manifest with the largest time stamp.
func (r *mapBasedRepository) GetLatestManifest(deploymentName string) (*common.Manifest, error) {
r.Lock()
defer r.Unlock()
d, err := r.GetValidDeployment(deploymentName)
if err != nil {
return nil, err
}
return r.getManifestForDeployment(deploymentName, d.LatestManifest)
}
// ListTypes returns all types known from existing instances.
func (r *mapBasedRepository) ListTypes() []string {
var keys []string
for k := range r.instances {
keys = append(keys, k)
}
return keys
}
// GetTypeInstances returns all instances of a given type. If type is empty,
// returns all instances for all types.
func (r *mapBasedRepository) GetTypeInstances(typeName string) []*common.TypeInstance {
r.Lock()
defer r.Unlock()
var instances []*common.TypeInstance
for t, dInstMap := range r.instances {
if t == typeName || typeName == "all" {
for _, i := range dInstMap {
instances = append(instances, i...)
}
}
}
return instances
}
// ClearTypeInstances deletes all instances associated with the given
// deployment name from the type instance repository.
func (r *mapBasedRepository) ClearTypeInstances(deploymentName string) {
r.Lock()
defer r.Unlock()
for t, dMap := range r.instances {
delete(dMap, deploymentName)
if len(dMap) == 0 {
delete(r.instances, t)
}
}
}
// SetTypeInstances sets all type instances for a given deployment name.
//
// To clear the current set of instances first, caller should first use
// ClearTypeInstances().
func (r *mapBasedRepository) SetTypeInstances(deploymentName string, instances map[string][]*common.TypeInstance) {
r.Lock()
defer r.Unlock()
// Add each instance list to the appropriate type map.
for t, is := range instances {
if r.instances[t] == nil {
r.instances[t] = make(deploymentTypeInstanceMap)
}
r.instances[t][deploymentName] = is Close()
}
} }
...@@ -23,31 +23,28 @@ import ( ...@@ -23,31 +23,28 @@ import (
"testing" "testing"
) )
func TestRepositoryListEmpty(t *testing.T) { // TestRepositoryListEmpty checks that listing an empty repository works.
r := NewMapBasedRepository() func TestRepositoryListEmpty(t *testing.T, r Repository) {
d, err := r.ListDeployments() d, err := r.ListDeployments()
if err != nil { if err != nil {
t.Fatal("List Deployments failed") t.Fatal("List Deployments failed")
} }
if len(d) != 0 { if len(d) != 0 {
t.Fatal("Returned non zero list") t.Fatal("Returned non zero list")
} }
} }
func TestRepositoryGetFailsWithNonExistentDeployment(t *testing.T) { // TestRepositoryGetFailsWithNonExistentDeployment checks that getting a non-existent deployment fails.
r := NewMapBasedRepository() func TestRepositoryGetFailsWithNonExistentDeployment(t *testing.T, r Repository) {
_, err := r.GetDeployment("nothere") _, err := r.GetDeployment("nothere")
if err == nil { if err == nil {
t.Fatal("GetDeployment didn't fail with non-existent deployment") t.Fatal("GetDeployment didn't fail with non-existent deployment")
} }
if err.Error() != "deployment nothere not found" {
t.Fatal("Error message doesn't match")
}
} }
func testCreateDeploymentWithManifests(t *testing.T, count int) { func testCreateDeploymentWithManifests(t *testing.T, r Repository, count int) {
var deploymentName = "mydeployment" var deploymentName = "mydeployment"
r := NewMapBasedRepository()
d, err := r.CreateDeployment(deploymentName) d, err := r.CreateDeployment(deploymentName)
if err != nil { if err != nil {
...@@ -58,14 +55,16 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) { ...@@ -58,14 +55,16 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) {
if err != nil { if err != nil {
t.Fatalf("ListDeployments failed: %v", err) t.Fatalf("ListDeployments failed: %v", err)
} }
if len(l) != 1 { if len(l) != 1 {
t.Fatalf("List of deployments is not 1: %d", len(l)) t.Fatalf("Number of deployments listed is not 1: %d", len(l))
} }
dNew, err := r.GetDeployment(deploymentName) dNew, err := r.GetDeployment(deploymentName)
if err != nil { if err != nil {
t.Fatalf("GetDeployment failed: %v", err) t.Fatalf("GetDeployment failed: %v", err)
} }
if dNew.Name != d.Name { if dNew.Name != d.Name {
t.Fatalf("Deployment Names don't match, got: %v, expected %v", dNew, d) t.Fatalf("Deployment Names don't match, got: %v, expected %v", dNew, d)
} }
...@@ -74,6 +73,7 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) { ...@@ -74,6 +73,7 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) {
if err != nil { if err != nil {
t.Fatalf("ListManifests failed: %v", err) t.Fatalf("ListManifests failed: %v", err)
} }
if len(mList) != 0 { if len(mList) != 0 {
t.Fatalf("Deployment has non-zero manifest count: %d", len(mList)) t.Fatalf("Deployment has non-zero manifest count: %d", len(mList))
} }
...@@ -81,10 +81,11 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) { ...@@ -81,10 +81,11 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) {
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
var manifestName = fmt.Sprintf("manifest-%d", i) var manifestName = fmt.Sprintf("manifest-%d", i)
manifest := common.Manifest{Deployment: deploymentName, Name: manifestName} manifest := common.Manifest{Deployment: deploymentName, Name: manifestName}
err := r.AddManifest(deploymentName, &manifest) err := r.AddManifest(&manifest)
if err != nil { if err != nil {
t.Fatalf("AddManifest failed: %v", err) t.Fatalf("AddManifest failed: %v", err)
} }
d, err = r.GetDeployment(deploymentName) d, err = r.GetDeployment(deploymentName)
if err != nil { if err != nil {
t.Fatalf("GetDeployment failed: %v", err) t.Fatalf("GetDeployment failed: %v", err)
...@@ -98,6 +99,7 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) { ...@@ -98,6 +99,7 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) {
if err != nil { if err != nil {
t.Fatalf("ListManifests failed: %v", err) t.Fatalf("ListManifests failed: %v", err)
} }
if len(mListNew) != i+1 { if len(mListNew) != i+1 {
t.Fatalf("Deployment has unexpected manifest count: want %d, have %d", i+1, len(mListNew)) t.Fatalf("Deployment has unexpected manifest count: want %d, have %d", i+1, len(mListNew))
} }
...@@ -106,98 +108,110 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) { ...@@ -106,98 +108,110 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) {
if err != nil { if err != nil {
t.Fatalf("GetManifest failed: %v", err) t.Fatalf("GetManifest failed: %v", err)
} }
if m.Name != manifestName { if m.Name != manifestName {
t.Fatalf("Unexpected manifest name: want %s, have %s", manifestName, m.Name) t.Fatalf("Unexpected manifest name: want %s, have %s", manifestName, m.Name)
} }
} }
} }
func TestRepositoryCreateDeploymentWorks(t *testing.T) { // TestRepositoryCreateDeploymentWorks checks that creating a deployment works.
testCreateDeploymentWithManifests(t, 1) func TestRepositoryCreateDeploymentWorks(t *testing.T, r Repository) {
testCreateDeploymentWithManifests(t, r, 1)
} }
func TestRepositoryMultipleManifestsWorks(t *testing.T) { // TestRepositoryMultipleManifestsWorks checks that creating a deploymente with multiple manifests works.
testCreateDeploymentWithManifests(t, 7) func TestRepositoryMultipleManifestsWorks(t *testing.T, r Repository) {
testCreateDeploymentWithManifests(t, r, 7)
} }
func TestRepositoryDeleteFailsWithNonExistentDeployment(t *testing.T) { // TestRepositoryDeleteFailsWithNonExistentDeployment checks that deleting a non-existent deployment fails.
func TestRepositoryDeleteFailsWithNonExistentDeployment(t *testing.T, r Repository) {
var deploymentName = "mydeployment" var deploymentName = "mydeployment"
r := NewMapBasedRepository()
d, err := r.DeleteDeployment(deploymentName, false) d, err := r.DeleteDeployment(deploymentName, false)
if err == nil { if err == nil {
t.Fatalf("DeleteDeployment didn't fail with non existent deployment") t.Fatalf("DeleteDeployment didn't fail with non existent deployment")
} }
if d != nil { if d != nil {
t.Fatalf("DeleteDeployment returned non-nil for non existent deployment") t.Fatalf("DeleteDeployment returned non-nil for non existent deployment")
} }
} }
func TestRepositoryDeleteWorksWithNoLatestManifest(t *testing.T) { // TestRepositoryDeleteWorksWithNoLatestManifest checks that deleting a deployment with no latest manifest works.
func TestRepositoryDeleteWorksWithNoLatestManifest(t *testing.T, r Repository) {
var deploymentName = "mydeployment" var deploymentName = "mydeployment"
r := NewMapBasedRepository()
_, err := r.CreateDeployment(deploymentName) _, err := r.CreateDeployment(deploymentName)
if err != nil { if err != nil {
t.Fatalf("CreateDeployment failed: %v", err) t.Fatalf("CreateDeployment failed: %v", err)
} }
dDeleted, err := r.DeleteDeployment(deploymentName, false) dDeleted, err := r.DeleteDeployment(deploymentName, false)
if err != nil { if err != nil {
t.Fatalf("DeleteDeployment failed: %v", err) t.Fatalf("DeleteDeployment failed: %v", err)
} }
if dDeleted.State.Status != common.DeletedStatus { if dDeleted.State.Status != common.DeletedStatus {
t.Fatalf("Deployment Status is not deleted") t.Fatalf("Deployment Status is not deleted")
} }
if _, err := r.ListManifests(deploymentName); err == nil { if _, err := r.ListManifests(deploymentName); err == nil {
t.Fatalf("Manifests are not deleted") t.Fatalf("Manifests are not deleted")
} }
} }
func TestRepositoryDeleteDeploymentWorksNoForget(t *testing.T) { // TestRepositoryDeleteDeploymentWorksNoForget checks that deleting a deployment without forgetting it works.
func TestRepositoryDeleteDeploymentWorksNoForget(t *testing.T, r Repository) {
var deploymentName = "mydeployment" var deploymentName = "mydeployment"
var manifestName = "manifest-0" var manifestName = "manifest-0"
r := NewMapBasedRepository()
manifest := common.Manifest{Deployment: deploymentName, Name: manifestName} manifest := common.Manifest{Deployment: deploymentName, Name: manifestName}
_, err := r.CreateDeployment(deploymentName) _, err := r.CreateDeployment(deploymentName)
if err != nil { if err != nil {
t.Fatalf("CreateDeployment failed: %v", err) t.Fatalf("CreateDeployment failed: %v", err)
} }
err = r.AddManifest(deploymentName, &manifest)
err = r.AddManifest(&manifest)
if err != nil { if err != nil {
t.Fatalf("AddManifest failed: %v", err) t.Fatalf("AddManifest failed: %v", err)
} }
dDeleted, err := r.DeleteDeployment(deploymentName, false) dDeleted, err := r.DeleteDeployment(deploymentName, false)
if err != nil { if err != nil {
t.Fatalf("DeleteDeployment failed: %v", err) t.Fatalf("DeleteDeployment failed: %v", err)
} }
if dDeleted.State.Status != common.DeletedStatus { if dDeleted.State.Status != common.DeletedStatus {
t.Fatalf("Deployment Status is not deleted") t.Fatalf("Deployment Status is not deleted")
} }
} }
func TestRepositoryDeleteDeploymentWorksForget(t *testing.T) { // TestRepositoryDeleteDeploymentWorksForget checks that deleting and forgetting a deployment works.
func TestRepositoryDeleteDeploymentWorksForget(t *testing.T, r Repository) {
var deploymentName = "mydeployment" var deploymentName = "mydeployment"
var manifestName = "manifest-0" var manifestName = "manifest-0"
r := NewMapBasedRepository()
manifest := common.Manifest{Deployment: deploymentName, Name: manifestName} manifest := common.Manifest{Deployment: deploymentName, Name: manifestName}
_, err := r.CreateDeployment(deploymentName) _, err := r.CreateDeployment(deploymentName)
if err != nil { if err != nil {
t.Fatalf("CreateDeployment failed: %v", err) t.Fatalf("CreateDeployment failed: %v", err)
} }
err = r.AddManifest(deploymentName, &manifest)
err = r.AddManifest(&manifest)
if err != nil { if err != nil {
t.Fatalf("AddManifest failed: %v", err) t.Fatalf("AddManifest failed: %v", err)
} }
dDeleted, err := r.DeleteDeployment(deploymentName, true) dDeleted, err := r.DeleteDeployment(deploymentName, true)
if err != nil { if err != nil {
t.Fatalf("DeleteDeployment failed: %v", err) t.Fatalf("DeleteDeployment failed: %v", err)
} }
if dDeleted.State.Status != common.CreatedStatus { if dDeleted.State.Status != common.CreatedStatus {
t.Fatalf("Deployment Status is not created") t.Fatalf("Deployment Status is not created")
} }
} }
func TestRepositoryTypeInstances(t *testing.T) { // TestRepositoryTypeInstances checks that type instances can be listed and retrieved successfully.
r := NewMapBasedRepository() func TestRepositoryTypeInstances(t *testing.T, r Repository) {
d1Map := map[string][]*common.TypeInstance{ d1Map := map[string][]*common.TypeInstance{
"t1": []*common.TypeInstance{ "t1": []*common.TypeInstance{
&common.TypeInstance{ &common.TypeInstance{
...@@ -234,46 +248,93 @@ func TestRepositoryTypeInstances(t *testing.T) { ...@@ -234,46 +248,93 @@ func TestRepositoryTypeInstances(t *testing.T) {
}, },
} }
if instances := r.GetTypeInstances("noinstances"); len(instances) != 0 { instances, err := r.GetTypeInstances("noinstances")
if err != nil {
t.Fatal(err)
}
if len(instances) != 0 {
t.Fatalf("expected no instances: %v", instances) t.Fatalf("expected no instances: %v", instances)
} }
if types := r.ListTypes(); len(types) != 0 { types, err := r.ListTypes()
if err != nil {
t.Fatal(err)
}
if len(types) != 0 {
t.Fatalf("expected no types: %v", types) t.Fatalf("expected no types: %v", types)
} }
r.SetTypeInstances("d1", d1Map) r.AddTypeInstances(d1Map)
r.SetTypeInstances("d2", d2Map) r.AddTypeInstances(d2Map)
r.SetTypeInstances("d3", d3Map) r.AddTypeInstances(d3Map)
if instances := r.GetTypeInstances("unknowntype"); len(instances) != 0 { instances, err = r.GetTypeInstances("unknowntype")
if err != nil {
t.Fatal(err)
}
if len(instances) != 0 {
t.Fatalf("expected no instances: %v", instances) t.Fatalf("expected no instances: %v", instances)
} }
if instances := r.GetTypeInstances("t1"); len(instances) != 1 { instances, err = r.GetTypeInstances("t1")
if err != nil {
t.Fatal(err)
}
if len(instances) != 1 {
t.Fatalf("expected one instance: %v", instances) t.Fatalf("expected one instance: %v", instances)
} }
if instances := r.GetTypeInstances("t2"); len(instances) != 2 { instances, err = r.GetTypeInstances("t2")
if err != nil {
t.Fatal(err)
}
if len(instances) != 2 {
t.Fatalf("expected two instances: %v", instances) t.Fatalf("expected two instances: %v", instances)
} }
if instances := r.GetTypeInstances("all"); len(instances) != 3 { instances, err = r.GetTypeInstances("all")
if err != nil {
t.Fatal(err)
}
if len(instances) != 3 {
t.Fatalf("expected three total instances: %v", instances) t.Fatalf("expected three total instances: %v", instances)
} }
if types := r.ListTypes(); len(types) != 2 { types, err = r.ListTypes()
if err != nil {
t.Fatal(err)
}
if len(types) != 2 {
t.Fatalf("expected two total types: %v", types) t.Fatalf("expected two total types: %v", types)
} }
r.ClearTypeInstances("d1") err = r.ClearTypeInstancesForDeployment("d1")
if instances := r.GetTypeInstances("t1"); len(instances) != 0 { if err != nil {
t.Fatal(err)
}
instances, err = r.GetTypeInstances("t1")
if err != nil {
t.Fatal(err)
}
if len(instances) != 0 {
t.Fatalf("expected no instances after clear: %v", instances) t.Fatalf("expected no instances after clear: %v", instances)
} }
if types := r.ListTypes(); len(types) != 1 { types, err = r.ListTypes()
if err != nil {
t.Fatal(err)
}
if len(types) != 1 {
t.Fatalf("expected one total type: %v", types) t.Fatalf("expected one total type: %v", types)
} }
} }
// TODO(vaikas): Add more tests
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package transient implements a transient deployment repository.
package transient
import (
"fmt"
"log"
"sync"
"time"
"github.com/kubernetes/deployment-manager/common"
"github.com/kubernetes/deployment-manager/manager/repository"
)
// deploymentTypeInstanceMap stores type instances mapped by deployment name.
// This allows for simple updating and deleting of per-deployment instances
// when deployments are created/updated/deleted.
type deploymentTypeInstanceMap map[string][]*common.TypeInstance
type tRepository struct {
sync.RWMutex
deployments map[string]common.Deployment
manifests map[string]map[string]*common.Manifest
instances map[string]deploymentTypeInstanceMap
}
// NewRepository returns a new transient repository. Its lifetime is coupled
// to the lifetime of the current process. When the process dies, its contents
// will be permanently destroyed.
func NewRepository() repository.Repository {
return &tRepository{
deployments: make(map[string]common.Deployment, 0),
manifests: make(map[string]map[string]*common.Manifest, 0),
instances: make(map[string]deploymentTypeInstanceMap, 0),
}
}
func (r *tRepository) Close() {
r.deployments = make(map[string]common.Deployment, 0)
r.manifests = make(map[string]map[string]*common.Manifest, 0)
r.instances = make(map[string]deploymentTypeInstanceMap, 0)
}
// ListDeployments returns of all of the deployments in the repository.
func (r *tRepository) ListDeployments() ([]common.Deployment, error) {
l := []common.Deployment{}
for _, deployment := range r.deployments {
l = append(l, deployment)
}
return l, nil
}
// GetDeployment returns the deployment with the supplied name.
// If the deployment is not found, it returns an error.
func (r *tRepository) GetDeployment(name string) (*common.Deployment, error) {
d, ok := r.deployments[name]
if !ok {
return nil, fmt.Errorf("deployment %s not found", name)
}
return &d, nil
}
// GetValidDeployment returns the deployment with the supplied name.
// If the deployment is not found or marked as deleted, it returns an error.
func (r *tRepository) GetValidDeployment(name string) (*common.Deployment, error) {
d, err := r.GetDeployment(name)
if err != nil {
return nil, err
}
if d.State.Status == common.DeletedStatus {
return nil, fmt.Errorf("deployment %s is deleted", name)
}
return d, nil
}
// SetDeploymentState sets the DeploymentState of the deployment and updates ModifiedAt
func (r *tRepository) SetDeploymentState(name string, state *common.DeploymentState) error {
r.Lock()
defer r.Unlock()
d, err := r.GetValidDeployment(name)
if err != nil {
return err
}
d.State = state
d.ModifiedAt = time.Now()
r.deployments[name] = *d
return nil
}
// CreateDeployment creates a new deployment and stores it in the repository.
func (r *tRepository) CreateDeployment(name string) (*common.Deployment, error) {
r.Lock()
defer r.Unlock()
exists, _ := r.GetValidDeployment(name)
if exists != nil {
return nil, fmt.Errorf("Deployment %s already exists", name)
}
d := common.NewDeployment(name)
r.deployments[name] = *d
log.Printf("created deployment: %v", d)
return d, nil
}
// AddManifest adds a manifest to the repository and repoints the latest
// manifest to it for the corresponding deployment.
func (r *tRepository) AddManifest(manifest *common.Manifest) error {
r.Lock()
defer r.Unlock()
deploymentName := manifest.Deployment
l, err := r.ListManifests(deploymentName)
if err != nil {
return err
}
// Make sure the manifest doesn't already exist, and if not, add the manifest to
// map of manifests this deployment has
if _, ok := l[manifest.Name]; ok {
return fmt.Errorf("Manifest %s already exists in deployment %s", manifest.Name, deploymentName)
}
d, err := r.GetValidDeployment(deploymentName)
if err != nil {
return err
}
l[manifest.Name] = manifest
d.LatestManifest = manifest.Name
d.ModifiedAt = time.Now()
r.deployments[deploymentName] = *d
log.Printf("Added manifest %s to deployment: %s", manifest.Name, deploymentName)
return nil
}
// SetManifest sets an existing manifest in the repository to provided manifest.
func (r *tRepository) SetManifest(manifest *common.Manifest) error {
r.Lock()
defer r.Unlock()
l, err := r.ListManifests(manifest.Deployment)
if err != nil {
return err
}
if _, ok := l[manifest.Name]; !ok {
return fmt.Errorf("manifest %s not found", manifest.Name)
}
l[manifest.Name] = manifest
return nil
}
// DeleteDeployment deletes the deployment with the supplied name.
// If forget is true, then the deployment is removed from the repository.
// Otherwise, it is marked as deleted and retained.
func (r *tRepository) DeleteDeployment(name string, forget bool) (*common.Deployment, error) {
r.Lock()
defer r.Unlock()
d, err := r.GetValidDeployment(name)
if err != nil {
return nil, err
}
if !forget {
d.DeletedAt = time.Now()
d.State = &common.DeploymentState{Status: common.DeletedStatus}
r.deployments[name] = *d
} else {
delete(r.deployments, name)
delete(r.manifests, name)
d.LatestManifest = ""
}
log.Printf("deleted deployment: %v", d)
return d, nil
}
func (r *tRepository) ListManifests(deploymentName string) (map[string]*common.Manifest, error) {
_, err := r.GetValidDeployment(deploymentName)
if err != nil {
return nil, err
}
return r.listManifestsForDeployment(deploymentName)
}
func (r *tRepository) listManifestsForDeployment(deploymentName string) (map[string]*common.Manifest, error) {
l, ok := r.manifests[deploymentName]
if !ok {
l = make(map[string]*common.Manifest, 0)
r.manifests[deploymentName] = l
}
return l, nil
}
func (r *tRepository) GetManifest(deploymentName string, manifestName string) (*common.Manifest, error) {
_, err := r.GetValidDeployment(deploymentName)
if err != nil {
return nil, err
}
return r.getManifestForDeployment(deploymentName, manifestName)
}
func (r *tRepository) getManifestForDeployment(deploymentName string, manifestName string) (*common.Manifest, error) {
l, err := r.listManifestsForDeployment(deploymentName)
if err != nil {
return nil, err
}
m, ok := l[manifestName]
if !ok {
return nil, fmt.Errorf("manifest %s not found in deployment %s", manifestName, deploymentName)
}
return m, nil
}
// GetLatestManifest returns the latest manifest for a given deployment,
// which by definition is the manifest with the largest time stamp.
func (r *tRepository) GetLatestManifest(deploymentName string) (*common.Manifest, error) {
d, err := r.GetValidDeployment(deploymentName)
if err != nil {
return nil, err
}
if d.LatestManifest == "" {
return nil, nil
}
return r.getManifestForDeployment(deploymentName, d.LatestManifest)
}
// ListTypes returns all types known from existing instances.
func (r *tRepository) ListTypes() ([]string, error) {
var keys []string
for k := range r.instances {
keys = append(keys, k)
}
return keys, nil
}
// GetTypeInstances returns all instances of a given type. If type is empty,
// returns all instances for all types.
func (r *tRepository) GetTypeInstances(typeName string) ([]*common.TypeInstance, error) {
var instances []*common.TypeInstance
for t, dInstMap := range r.instances {
if t == typeName || typeName == "" || typeName == "all" {
for _, i := range dInstMap {
instances = append(instances, i...)
}
}
}
return instances, nil
}
// ClearTypeInstancesForDeployment deletes all type instances associated with the given
// deployment from the repository.
func (r *tRepository) ClearTypeInstancesForDeployment(deploymentName string) error {
r.Lock()
defer r.Unlock()
for t, dMap := range r.instances {
delete(dMap, deploymentName)
if len(dMap) == 0 {
delete(r.instances, t)
}
}
return nil
}
// AddTypeInstances adds the supplied type instances to the repository.
func (r *tRepository) AddTypeInstances(instances map[string][]*common.TypeInstance) error {
r.Lock()
defer r.Unlock()
// Add instances to the appropriate type and deployment maps.
for t, is := range instances {
if r.instances[t] == nil {
r.instances[t] = make(deploymentTypeInstanceMap)
}
tmap := r.instances[t]
for _, instance := range is {
deployment := instance.Deployment
if tmap[deployment] == nil {
tmap[deployment] = make([]*common.TypeInstance, 0)
}
tmap[deployment] = append(tmap[deployment], instance)
}
}
return nil
}
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package transient
import (
"github.com/kubernetes/deployment-manager/manager/repository"
"testing"
)
func TestRepositoryListEmpty(t *testing.T) {
repository.TestRepositoryListEmpty(t, NewRepository())
}
func TestRepositoryGetFailsWithNonExistentDeployment(t *testing.T) {
repository.TestRepositoryGetFailsWithNonExistentDeployment(t, NewRepository())
}
func TestRepositoryCreateDeploymentWorks(t *testing.T) {
repository.TestRepositoryCreateDeploymentWorks(t, NewRepository())
}
func TestRepositoryMultipleManifestsWorks(t *testing.T) {
repository.TestRepositoryMultipleManifestsWorks(t, NewRepository())
}
func TestRepositoryDeleteFailsWithNonExistentDeployment(t *testing.T) {
repository.TestRepositoryDeleteFailsWithNonExistentDeployment(t, NewRepository())
}
func TestRepositoryDeleteWorksWithNoLatestManifest(t *testing.T) {
repository.TestRepositoryDeleteWorksWithNoLatestManifest(t, NewRepository())
}
func TestRepositoryDeleteDeploymentWorksNoForget(t *testing.T) {
repository.TestRepositoryDeleteDeploymentWorksNoForget(t, NewRepository())
}
func TestRepositoryDeleteDeploymentWorksForget(t *testing.T) {
repository.TestRepositoryDeleteDeploymentWorksForget(t, NewRepository())
}
func TestRepositoryTypeInstances(t *testing.T) {
repository.TestRepositoryTypeInstances(t, NewRepository())
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment