First set of changes for persistent repository.

pull/170/head
jackgr 9 years ago
parent e1a76a409c
commit fdc16739f6

@ -35,7 +35,6 @@ type Schema struct {
// the creation, modification and/or deletion of a set of resources.
type Deployment struct {
Name string `json:"name"`
ID int `json:"id"`
CreatedAt time.Time `json:"createdAt,omitempty"`
DeployedAt time.Time `json:"deployedAt,omitempty"`
ModifiedAt time.Time `json:"modifiedAt,omitempty"`

@ -0,0 +1,167 @@
######################################################################
# 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 (
"github.com/kubernetes/deployment-manager/common"
"github.com/kubernetes/deployment-manager/manager/manager"
"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/util"
)
@ -69,6 +71,9 @@ var (
deployerURL = flag.String("deployerURL", "", "The URL for the deployer service.")
credentialFile = flag.String("credentialFile", "", "Local file to use 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
@ -97,27 +102,39 @@ func init() {
backend = newManager(credentialProvider)
}
const expanderPort = "8080"
const deployerPort = "8080"
func newManager(cp common.CredentialProvider) manager.Manager {
service := registry.NewInmemRegistryService()
registryProvider := registry.NewDefaultRegistryProvider(cp, service)
resolver := manager.NewTypeResolver(registryProvider, util.DefaultHTTPClient())
expander := manager.NewExpander(getServiceURL(*expanderURL, *expanderName), resolver)
deployer := manager.NewDeployer(getServiceURL(*deployerURL, *deployerName))
r := repository.NewMapBasedRepository()
credentialProvider := cp
return manager.NewManager(expander, deployer, r, registryProvider, service, credentialProvider)
expander := manager.NewExpander(getServiceURL(*expanderURL, *expanderName, expanderPort), resolver)
deployer := manager.NewDeployer(getServiceURL(*deployerURL, *deployerName, deployerPort))
address := strings.TrimPrefix(getServiceURL(*mongoAddress, *mongoName, *mongoPort), "https://")
repository := createRepository(address)
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 == "" {
serviceURL = makeEnvVariableURL(serviceName)
if serviceURL == "" {
addrs, err := net.LookupHost(serviceName)
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 {
func makeEnvVariableURL(str string) string {
prefix := makeEnvVariableName(str)
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,
@ -335,7 +352,13 @@ func expandHandlerFunc(w http.ResponseWriter, r *http.Request) {
func listTypesHandlerFunc(w http.ResponseWriter, r *http.Request) {
handler := "manager: list types"
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) {
@ -346,7 +369,13 @@ func listTypeInstancesHandlerFunc(w http.ResponseWriter, r *http.Request) {
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

@ -21,11 +21,11 @@ import (
"fmt"
"io"
"io/ioutil"
"time"
"log"
"net/http"
"net/url"
"strings"
"time"
"github.com/ghodss/yaml"
"github.com/kubernetes/deployment-manager/common"
@ -47,7 +47,7 @@ func NewDeployer(url string) Deployer {
type deployer struct {
deployerURL string
timeout int
timeout int
}
func (d *deployer) getBaseURL() string {

@ -44,8 +44,8 @@ type Manager interface {
Expand(t *common.Template) (*common.Manifest, error)
// Types
ListTypes() []string
ListInstances(typeName string) []*common.TypeInstance
ListTypes() ([]string, error)
ListInstances(typeName string) ([]*common.TypeInstance, error)
// Registries
ListRegistries() ([]*common.Registry, error)
@ -141,7 +141,7 @@ func (m *manager) CreateDeployment(t *common.Template) (*common.Deployment, erro
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)
m.repository.SetDeploymentState(t.Name, failState(err))
return nil, err
@ -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
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)
m.repository.SetDeploymentState(t.Name, failState(err))
return nil, err
}
// 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)
}
@ -200,15 +200,15 @@ func (m *manager) createManifest(t *common.Template) (*common.Manifest, error) {
}, nil
}
func (m *manager) addTypeInstances(deploymentName string, manifestName string, layout *common.Layout) {
m.repository.ClearTypeInstances(deploymentName)
func (m *manager) setTypeInstances(deploymentName string, manifestName string, layout *common.Layout) {
m.repository.ClearTypeInstancesForDeployment(deploymentName)
instances := make(map[string][]*common.TypeInstance)
for i, r := range layout.Resources {
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) {
@ -220,6 +220,7 @@ func addTypeInstances(instances *map[string][]*common.TypeInstance, r *common.La
Manifest: manifestName,
Path: jsonPath,
}
(*instances)[r.Type] = append((*instances)[r.Type], inst)
// Add all sub resources if they exist.
@ -260,11 +261,12 @@ func (m *manager) DeleteDeployment(name string, forget bool) (*common.Deployment
}
// Create an empty manifest since resources have been deleted.
err = m.repository.AddManifest(name, &common.Manifest{Deployment: name, Name: generateManifestName()})
if err != nil {
log.Printf("Failed to add empty manifest")
m.repository.SetDeploymentState(name, failState(err))
return nil, err
if !forget {
manifest := &common.Manifest{Deployment: name, Name: generateManifestName()}
if err := m.repository.AddManifest(manifest); err != nil {
log.Printf("Failed to add empty manifest")
return nil, err
}
}
}
@ -274,8 +276,7 @@ func (m *manager) DeleteDeployment(name string, forget bool) (*common.Deployment
}
// Finally remove the type instances for this deployment.
m.repository.ClearTypeInstances(name)
m.repository.ClearTypeInstancesForDeployment(name)
return d, nil
}
@ -301,15 +302,14 @@ func (m *manager) PutDeployment(name string, t *common.Template) (*common.Deploy
}
manifest.ExpandedConfig = actualConfig
err = m.repository.AddManifest(t.Name, manifest)
err = m.repository.AddManifest(manifest)
if err != nil {
m.repository.SetDeploymentState(name, failState(err))
return nil, err
}
// 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)
}
@ -326,11 +326,11 @@ func (m *manager) Expand(t *common.Template) (*common.Manifest, error) {
}, nil
}
func (m *manager) ListTypes() []string {
func (m *manager) ListTypes() ([]string, error) {
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)
}

@ -160,6 +160,7 @@ func newRepositoryStub() *repositoryStub {
return ret
}
// Deployments.
func (repository *repositoryStub) ListDeployments() ([]common.Deployment, error) {
if repository.FailListDeployments {
return deploymentList, errTest
@ -181,11 +182,6 @@ func (repository *repositoryStub) GetValidDeployment(d string) (*common.Deployme
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) {
repository.Created = append(repository.Created, d)
return &deployment, nil
@ -196,22 +192,20 @@ func (repository *repositoryStub) DeleteDeployment(d string, forget bool) (*comm
return &deployment, nil
}
func (repository *repositoryStub) AddManifest(d string, manifest *common.Manifest) error {
repository.ManifestAdd[d] = manifest
func (repository *repositoryStub) SetDeploymentState(name string, state *common.DeploymentState) error {
repository.DeploymentStates = append(repository.DeploymentStates, state)
return nil
}
func (repository *repositoryStub) SetManifest(d string, manifest *common.Manifest) error {
repository.ManifestSet[d] = manifest
// Manifests.
func (repository *repositoryStub) AddManifest(manifest *common.Manifest) error {
repository.ManifestAdd[manifest.Deployment] = manifest
return nil
}
func (repository *repositoryStub) GetLatestManifest(d string) (*common.Manifest, error) {
if d == deploymentName {
return repository.ManifestAdd[d], nil
}
return nil, errTest
func (repository *repositoryStub) SetManifest(manifest *common.Manifest) error {
repository.ManifestSet[manifest.Deployment] = manifest
return nil
}
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
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
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
return []*common.TypeInstance{}
return []*common.TypeInstance{}, nil
}
func (repository *repositoryStub) ClearTypeInstances(d string) {
func (repository *repositoryStub) ClearTypeInstancesForDeployment(d string) error {
repository.TypeInstancesCleared = true
return nil
}
func (repository *repositoryStub) SetTypeInstances(d string, is map[string][]*common.TypeInstance) {
for k := range is {
repository.TypeInstances[d] = append(repository.TypeInstances[d], k)
func (repository *repositoryStub) AddTypeInstances(is map[string][]*common.TypeInstance) error {
for t, instances := range is {
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 testRepository = newRepositoryStub()
var testDeployer = newDeployerStub()

@ -0,0 +1,488 @@
/*
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 implements a persistent deployment repository.
//
// This package is currently implemented using MondoDB, but there is no
// guarantee that it will continue to be implemented using MondoDB in the
// future.
package persistent
import (
"fmt"
"log"
"net/url"
"os"
"time"
"github.com/kubernetes/deployment-manager/common"
"github.com/kubernetes/deployment-manager/manager/repository"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type pDeployment struct {
ID string `bson:"_id"`
common.Deployment
}
type pManifest struct {
ID string `bson:"_id"`
common.Manifest
}
type pInstance struct {
ID string `bson:"_id"`
common.TypeInstance
}
type pRepository struct {
Session *mgo.Session // mongodb session
Deployments *mgo.Collection // deployments collection
Manifests *mgo.Collection // manifests collection
Instances *mgo.Collection // instances collection
}
// Constants used to configure the MongoDB database.
const (
DatabaseName = "deployment_manager"
DeploymentsCollectionName = "deployments_collection"
ManifestsCollectionName = "manifests_collection"
InstancesCollectionName = "instances_collection"
)
// NewRepository returns a new persistent repository. Its lifetime is decopuled
// from the lifetime of the current process. When the process dies, its contents
// will not be affected.
//
// The server argument provides connection information for the repository server.
// It is parsed as a URL, and the username, password, host and port, if provided,
// are used to create the connection string.
func NewRepository(server string) (repository.Repository, error) {
travis := os.Getenv("TRAVIS")
if travis == "true" {
err := fmt.Errorf("cannot use MongoDB in Travis CI due to gopkg.in/mgo.v2 issue #218")
log.Println(err.Error())
return nil, err
}
u, err := url.Parse(server)
if err != nil {
err2 := fmt.Errorf("cannot parse url '%s': %s\n", server, err)
log.Println(err2.Error())
return nil, err2
}
u2 := &url.URL{Scheme: "mongodb", User: u.User, Host: u.Host}
server = u2.String()
session, err := mgo.Dial(server)
if err != nil {
err2 := fmt.Errorf("cannot connect to MongoDB at %s: %s\n", server, err)
log.Println(err2.Error())
return nil, err2
}
session.SetMode(mgo.Strong, false)
session.SetSafe(&mgo.Safe{WMode: "majority"})
database := session.DB(DatabaseName)
deployments, err := createCollection(database, DeploymentsCollectionName, nil)
if err != nil {
return nil, err
}
manifests, err := createCollection(database, ManifestsCollectionName,
[][]string{{"manifest.deployment"}})
if err != nil {
return nil, err
}
instances, err := createCollection(database, InstancesCollectionName,
[][]string{{"typeinstance.type"}, {"typeinstance.deployment"}})
if err != nil {
return nil, err
}
pr := &pRepository{
Session: session,
Deployments: deployments,
Manifests: manifests,
Instances: instances,
}
return pr, nil
}
func createCollection(db *mgo.Database, cName string, keys [][]string) (*mgo.Collection, error) {
c := db.C(cName)
for _, key := range keys {
if err := createIndex(c, key...); err != nil {
return nil, err
}
}
return c, nil
}
func createIndex(c *mgo.Collection, key ...string) error {
if err := c.EnsureIndexKey(key...); err != nil {
err2 := fmt.Errorf("cannot create index %v for collection %s: %s\n", key, c.Name, err)
log.Println(err2.Error())
return err2
}
return nil
}
// Reset returns the repository to its initial state.
func (r *pRepository) Reset() error {
database := r.Session.DB(DatabaseName)
if err := database.DropDatabase(); err != nil {
return fmt.Errorf("cannot drop database %s", database.Name)
}
r.Close()
return nil
}
// Close cleans up any resources used by the repository.
func (r *pRepository) Close() {
r.Session.Close()
}
// ListDeployments returns of all of the deployments in the repository.
func (r *pRepository) ListDeployments() ([]common.Deployment, error) {
var result []pDeployment
if err := r.Deployments.Find(nil).All(&result); err != nil {
return nil, fmt.Errorf("cannot list deployments: %s", err)
}
deployments := []common.Deployment{}
for _, pd := range result {
deployments = append(deployments, pd.Deployment)
}
return deployments, nil
}
// GetDeployment returns the deployment with the supplied name.
// If the deployment is not found, it returns an error.
func (r *pRepository) GetDeployment(name string) (*common.Deployment, error) {
result := pDeployment{}
if err := r.Deployments.FindId(name).One(&result); err != nil {
return nil, fmt.Errorf("cannot get deployment %s: %s", name, err)
}
return &result.Deployment, 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 *pRepository) 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
}
// CreateDeployment creates a new deployment and stores it in the repository.
func (r *pRepository) CreateDeployment(name string) (*common.Deployment, error) {
exists, _ := r.GetValidDeployment(name)
if exists != nil {
return nil, fmt.Errorf("deployment %s already exists", name)
}
d := common.NewDeployment(name)
if err := r.insertDeployment(d); err != nil {
return nil, err
}
log.Printf("created deployment: %v", d)
return d, nil
}
// SetDeploymentStatus sets the DeploymentStatus of the deployment and updates ModifiedAt
func (r *pRepository) SetDeploymentState(name string, state *common.DeploymentState) error {
d, err := r.GetValidDeployment(name)
if err != nil {
return err
}
d.State = state
return r.updateDeployment(d)
}
func (r *pRepository) AddManifest(manifest *common.Manifest) error {
deploymentName := manifest.Deployment
d, err := r.GetValidDeployment(deploymentName)
if err != nil {
return err
}
count, err := r.Manifests.FindId(manifest.Name).Count()
if err != nil {
return fmt.Errorf("cannot search for manifest %s: %s", manifest.Name, err)
}
if count > 0 {
return fmt.Errorf("manifest %s already exists", manifest.Name)
}
if err := r.insertManifest(manifest); err != nil {
return err
}
d.LatestManifest = manifest.Name
if err := r.updateDeployment(d); err != nil {
return err
}
log.Printf("Added manifest %s to deployment: %s", manifest.Name, deploymentName)
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 *pRepository) DeleteDeployment(name string, forget bool) (*common.Deployment, error) {
d, err := r.GetValidDeployment(name)
if err != nil {
return nil, err
}
if !forget {
d.DeletedAt = time.Now()
d.State = &common.DeploymentState{Status: common.DeletedStatus}
if err := r.updateDeployment(d); err != nil {
return nil, err
}
} else {
d.LatestManifest = ""
if err := r.removeManifestsForDeployment(d); err != nil {
return nil, err
}
if err := r.removeDeployment(d); err != nil {
return nil, err
}
}
log.Printf("deleted deployment: %v", d)
return d, nil
}
func (r *pRepository) insertDeployment(d *common.Deployment) error {
if d != nil && d.Name != "" {
wrapper := pDeployment{ID: d.Name, Deployment: *d}
if err := r.Deployments.Insert(&wrapper); err != nil {
return fmt.Errorf("cannot insert deployment %v: %s", wrapper, err)
}
}
return nil
}
func (r *pRepository) removeDeployment(d *common.Deployment) error {
if d != nil && d.Name != "" {
if err := r.Deployments.RemoveId(d.Name); err != nil {
return fmt.Errorf("cannot remove deployment %s: %s", d.Name, err)
}
}
return nil
}
func (r *pRepository) updateDeployment(d *common.Deployment) error {
if d != nil && d.Name != "" {
if d.State.Status != common.DeletedStatus {
d.ModifiedAt = time.Now()
}
wrapper := pDeployment{ID: d.Name, Deployment: *d}
if err := r.Deployments.UpdateId(d.Name, &wrapper); err != nil {
return fmt.Errorf("cannot update deployment %v: %s", wrapper, err)
}
}
return nil
}
func (r *pRepository) ListManifests(deploymentName string) (map[string]*common.Manifest, error) {
_, err := r.GetValidDeployment(deploymentName)
if err != nil {
return nil, err
}
return r.listManifestsForDeployment(deploymentName)
}
func (r *pRepository) GetManifest(deploymentName string, manifestName string) (*common.Manifest, error) {
_, err := r.GetValidDeployment(deploymentName)
if err != nil {
return nil, err
}
return r.getManifestForDeployment(deploymentName, manifestName)
}
// GetLatestManifest returns the latest manifest for a given deployment,
// which by definition is the manifest with the largest time stamp.
func (r *pRepository) 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)
}
// SetManifest sets an existing manifest in the repository to provided manifest.
func (r *pRepository) SetManifest(manifest *common.Manifest) error {
_, err := r.GetManifest(manifest.Deployment, manifest.Name)
if err != nil {
return err
}
return r.updateManifest(manifest)
}
func (r *pRepository) updateManifest(m *common.Manifest) error {
if m != nil && m.Name != "" {
wrapper := pManifest{ID: m.Name, Manifest: *m}
if err := r.Manifests.UpdateId(m.Name, &wrapper); err != nil {
return fmt.Errorf("cannot update manifest %v: %s", wrapper, err)
}
}
return nil
}
func (r *pRepository) listManifestsForDeployment(deploymentName string) (map[string]*common.Manifest, error) {
query := bson.M{"manifest.deployment": deploymentName}
var result []pManifest
if err := r.Manifests.Find(query).All(&result); err != nil {
return nil, fmt.Errorf("cannot list manifests for deployment %s: %s", deploymentName, err)
}
l := make(map[string]*common.Manifest, 0)
for _, pm := range result {
l[pm.Name] = &pm.Manifest
}
return l, nil
}
func (r *pRepository) getManifestForDeployment(deploymentName string, manifestName string) (*common.Manifest, error) {
result := pManifest{}
if err := r.Manifests.FindId(manifestName).One(&result); err != nil {
return nil, fmt.Errorf("cannot get manifest %s: %s", manifestName, err)
}
if result.Deployment != deploymentName {
return nil, fmt.Errorf("manifest %s not found in deployment %s", manifestName, deploymentName)
}
return &result.Manifest, nil
}
func (r *pRepository) insertManifest(m *common.Manifest) error {
if m != nil && m.Name != "" {
wrapper := pManifest{ID: m.Name, Manifest: *m}
if err := r.Manifests.Insert(&wrapper); err != nil {
return fmt.Errorf("cannot insert manifest %v: %s", wrapper, err)
}
}
return nil
}
func (r *pRepository) removeManifestsForDeployment(d *common.Deployment) error {
if d != nil && d.Name != "" {
query := bson.M{"manifest.deployment": d.Name}
if _, err := r.Manifests.RemoveAll(query); err != nil {
return fmt.Errorf("cannot remove all manifests for deployment %s: %s", d.Name, err)
}
}
return nil
}
// ListTypes returns all types known from existing instances.
func (r *pRepository) ListTypes() ([]string, error) {
var result []string
if err := r.Instances.Find(nil).Distinct("typeinstance.type", &result); err != nil {
return nil, fmt.Errorf("cannot list type instances: %s", err)
}
return result, nil
}
// GetTypeInstances returns all instances of a given type. If typeName is empty
// or equal to "all", returns all instances of all types.
func (r *pRepository) GetTypeInstances(typeName string) ([]*common.TypeInstance, error) {
query := bson.M{"typeinstance.type": typeName}
if typeName == "" || typeName == "all" {
query = nil
}
var result []pInstance
if err := r.Instances.Find(query).All(&result); err != nil {
return nil, fmt.Errorf("cannot get instances of type %s: %s", typeName, err)
}
instances := []*common.TypeInstance{}
for _, pi := range result {
instances = append(instances, &pi.TypeInstance)
}
return instances, nil
}
// ClearTypeInstancesForDeployment deletes all type instances associated with the given
// deployment from the repository.
func (r *pRepository) ClearTypeInstancesForDeployment(deploymentName string) error {
if deploymentName != "" {
query := bson.M{"typeinstance.deployment": deploymentName}
if _, err := r.Instances.RemoveAll(query); err != nil {
return fmt.Errorf("cannot clear type instances for deployment %s: %s", deploymentName, err)
}
}
return nil
}
// AddTypeInstances adds the supplied type instances to the repository.
func (r *pRepository) AddTypeInstances(instances map[string][]*common.TypeInstance) error {
for _, is := range instances {
for _, i := range is {
key := fmt.Sprintf("%s.%s.%s", i.Deployment, i.Type, i.Name)
wrapper := pInstance{ID: key, TypeInstance: *i}
if err := r.Instances.Insert(&wrapper); err != nil {
return fmt.Errorf("cannot insert type instance %v: %s", wrapper, err)
}
}
}
return nil
}

@ -0,0 +1,110 @@
/*
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)
}
}

@ -6,7 +6,7 @@ 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.
@ -14,17 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Package repository implements a deployment repository using a map.
// It can be easily replaced by a deployment repository that uses some
// form of persistent storage.
// Package repository defines a deployment repository.
package repository
import (
"fmt"
"log"
"sync"
"time"
"github.com/kubernetes/deployment-manager/common"
)
@ -40,319 +33,17 @@ type Repository interface {
SetDeploymentState(name string, state *common.DeploymentState) error
// Manifests.
AddManifest(deploymentName string, manifest *common.Manifest) error
SetManifest(deploymentName string, manifest *common.Manifest) error
AddManifest(manifest *common.Manifest) error
SetManifest(manifest *common.Manifest) error
ListManifests(deploymentName string) (map[string]*common.Manifest, error)
GetManifest(deploymentName string, manifestName string) (*common.Manifest, error)
GetLatestManifest(deploymentName string) (*common.Manifest, error)
// Types.
ListTypes() []string
GetTypeInstances(typeName string) []*common.TypeInstance
ClearTypeInstances(deploymentName string)
SetTypeInstances(deploymentName string, instances map[string][]*common.TypeInstance)
}
// 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)
}
ListTypes() ([]string, error)
GetTypeInstances(typeName string) ([]*common.TypeInstance, error)
ClearTypeInstancesForDeployment(deploymentName string) error
AddTypeInstances(instances map[string][]*common.TypeInstance) error
r.instances[t][deploymentName] = is
}
Close()
}

@ -6,7 +6,7 @@ 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.
@ -23,31 +23,28 @@ import (
"testing"
)
func TestRepositoryListEmpty(t *testing.T) {
r := NewMapBasedRepository()
// TestRepositoryListEmpty checks that listing an empty repository works.
func TestRepositoryListEmpty(t *testing.T, r Repository) {
d, err := r.ListDeployments()
if err != nil {
t.Fatal("List Deployments failed")
}
if len(d) != 0 {
t.Fatal("Returned non zero list")
}
}
func TestRepositoryGetFailsWithNonExistentDeployment(t *testing.T) {
r := NewMapBasedRepository()
// TestRepositoryGetFailsWithNonExistentDeployment checks that getting a non-existent deployment fails.
func TestRepositoryGetFailsWithNonExistentDeployment(t *testing.T, r Repository) {
_, err := r.GetDeployment("nothere")
if err == nil {
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"
r := NewMapBasedRepository()
d, err := r.CreateDeployment(deploymentName)
if err != nil {
@ -58,14 +55,16 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) {
if err != nil {
t.Fatalf("ListDeployments failed: %v", err)
}
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)
if err != nil {
t.Fatalf("GetDeployment failed: %v", err)
}
if dNew.Name != d.Name {
t.Fatalf("Deployment Names don't match, got: %v, expected %v", dNew, d)
}
@ -74,6 +73,7 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) {
if err != nil {
t.Fatalf("ListManifests failed: %v", err)
}
if len(mList) != 0 {
t.Fatalf("Deployment has non-zero manifest count: %d", len(mList))
}
@ -81,10 +81,11 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) {
for i := 0; i < count; i++ {
var manifestName = fmt.Sprintf("manifest-%d", i)
manifest := common.Manifest{Deployment: deploymentName, Name: manifestName}
err := r.AddManifest(deploymentName, &manifest)
err := r.AddManifest(&manifest)
if err != nil {
t.Fatalf("AddManifest failed: %v", err)
}
d, err = r.GetDeployment(deploymentName)
if err != nil {
t.Fatalf("GetDeployment failed: %v", err)
@ -98,6 +99,7 @@ func testCreateDeploymentWithManifests(t *testing.T, count int) {
if err != nil {
t.Fatalf("ListManifests failed: %v", err)
}
if len(mListNew) != i+1 {
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) {
if err != nil {
t.Fatalf("GetManifest failed: %v", err)
}
if m.Name != manifestName {
t.Fatalf("Unexpected manifest name: want %s, have %s", manifestName, m.Name)
}
}
}
func TestRepositoryCreateDeploymentWorks(t *testing.T) {
testCreateDeploymentWithManifests(t, 1)
// TestRepositoryCreateDeploymentWorks checks that creating a deployment works.
func TestRepositoryCreateDeploymentWorks(t *testing.T, r Repository) {
testCreateDeploymentWithManifests(t, r, 1)
}
func TestRepositoryMultipleManifestsWorks(t *testing.T) {
testCreateDeploymentWithManifests(t, 7)
// TestRepositoryMultipleManifestsWorks checks that creating a deploymente with multiple manifests works.
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"
r := NewMapBasedRepository()
d, err := r.DeleteDeployment(deploymentName, false)
if err == nil {
t.Fatalf("DeleteDeployment didn't fail with non existent deployment")
}
if d != nil {
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"
r := NewMapBasedRepository()
_, err := r.CreateDeployment(deploymentName)
if err != nil {
t.Fatalf("CreateDeployment failed: %v", err)
}
dDeleted, err := r.DeleteDeployment(deploymentName, false)
if err != nil {
t.Fatalf("DeleteDeployment failed: %v", err)
}
if dDeleted.State.Status != common.DeletedStatus {
t.Fatalf("Deployment Status is not deleted")
}
if _, err := r.ListManifests(deploymentName); err == nil {
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 manifestName = "manifest-0"
r := NewMapBasedRepository()
manifest := common.Manifest{Deployment: deploymentName, Name: manifestName}
_, err := r.CreateDeployment(deploymentName)
if err != nil {
t.Fatalf("CreateDeployment failed: %v", err)
}
err = r.AddManifest(deploymentName, &manifest)
err = r.AddManifest(&manifest)
if err != nil {
t.Fatalf("AddManifest failed: %v", err)
}
dDeleted, err := r.DeleteDeployment(deploymentName, false)
if err != nil {
t.Fatalf("DeleteDeployment failed: %v", err)
}
if dDeleted.State.Status != common.DeletedStatus {
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 manifestName = "manifest-0"
r := NewMapBasedRepository()
manifest := common.Manifest{Deployment: deploymentName, Name: manifestName}
_, err := r.CreateDeployment(deploymentName)
if err != nil {
t.Fatalf("CreateDeployment failed: %v", err)
}
err = r.AddManifest(deploymentName, &manifest)
err = r.AddManifest(&manifest)
if err != nil {
t.Fatalf("AddManifest failed: %v", err)
}
dDeleted, err := r.DeleteDeployment(deploymentName, true)
if err != nil {
t.Fatalf("DeleteDeployment failed: %v", err)
}
if dDeleted.State.Status != common.CreatedStatus {
t.Fatalf("Deployment Status is not created")
}
}
func TestRepositoryTypeInstances(t *testing.T) {
r := NewMapBasedRepository()
// TestRepositoryTypeInstances checks that type instances can be listed and retrieved successfully.
func TestRepositoryTypeInstances(t *testing.T, r Repository) {
d1Map := map[string][]*common.TypeInstance{
"t1": []*common.TypeInstance{
&common.TypeInstance{
@ -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)
}
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)
}
r.SetTypeInstances("d1", d1Map)
r.SetTypeInstances("d2", d2Map)
r.SetTypeInstances("d3", d3Map)
r.AddTypeInstances(d1Map)
r.AddTypeInstances(d2Map)
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)
}
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)
}
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)
}
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)
}
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)
}
r.ClearTypeInstances("d1")
if instances := r.GetTypeInstances("t1"); len(instances) != 0 {
err = r.ClearTypeInstancesForDeployment("d1")
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)
}
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)
}
}
// TODO(vaikas): Add more tests

@ -0,0 +1,325 @@
/*
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
}

@ -0,0 +1,55 @@
/*
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())
}
Loading…
Cancel
Save