initial checking of provisioning container
diff --git a/build.gradle b/build.gradle
index 6c0babf..7e3ea90 100644
--- a/build.gradle
+++ b/build.gradle
@@ -58,6 +58,20 @@
commandLine "$dockerPath/docker", 'push', "$targetReg/cord-ip-allocator:$targetTag"
}
+task buildProvisionerImage(type: Exec) {
+ commandLine "$dockerPath/docker", 'build', '-t', 'cord-provisioner', './ip-allocator'
+}
+
+task tagProvisionerImage(type: Exec) {
+ dependsOn buildProvisionerImage
+ commandLine "$dockerPath/docker", 'tag', 'cord-provisioner', "$targetReg/cord-provisioner:$targetTag"
+}
+
+task publishProvisionerImage(type: Exec) {
+ dependsOn tagProvisionerImage
+ commandLine "$dockerPath/docker", 'push', "$targetReg/cord-provisioner:$targetTag"
+}
+
task buildAutomationImage(type: Exec) {
commandLine "$dockerPath/docker", 'build', '-t', "cord-maas-automation", "-f", "./automation/Dockerfile", "./automation"
}
@@ -132,6 +146,7 @@
dependsOn buildHarvesterImage
dependsOn buildAutomationImages
dependsOn buildAllocationImage
+ dependsOn buildProvisionerImage
}
task tagImages {
@@ -139,6 +154,7 @@
dependsOn tagHarvesterImage
dependsOn tagAutomationImages
dependsOn tagAllocationImage
+ dependsOn tagProvisionerImage
}
task publish {
@@ -146,6 +162,7 @@
dependsOn publishHarvesterImage
dependsOn publishAutomationImages
dependsOn publishAllocationImage
+ dependsOn publishProvisionerImage
}
// ~~~~~~~~~~~~~~~~~~~ Deployment / Test Tasks ~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/provisioner/Dockerfile b/provisioner/Dockerfile
new file mode 100644
index 0000000..478bff8
--- /dev/null
+++ b/provisioner/Dockerfile
@@ -0,0 +1,44 @@
+FROM ubuntu:14.04
+
+# Base image information borrowed by official golang wheezy Dockerfile
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ g++ \
+ gcc \
+ libc6-dev \
+ make \
+ curl \
+ && rm -rf /var/lib/apt/lists/*
+
+ENV GOLANG_VERSION 1.6.2
+ENV GOLANG_DOWNLOAD_URL https://golang.org/dl/go$GOLANG_VERSION.linux-amd64.tar.gz
+ENV GOLANG_DOWNLOAD_SHA256 e40c36ae71756198478624ed1bb4ce17597b3c19d243f3f0899bb5740d56212a
+
+RUN curl -kfsSL "$GOLANG_DOWNLOAD_URL" -o golang.tar.gz \
+ && echo "$GOLANG_DOWNLOAD_SHA256 golang.tar.gz" | sha256sum -c - \
+ && tar -C /usr/local -xzf golang.tar.gz \
+ && rm golang.tar.gz
+
+ENV GOPATH /go
+ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
+
+RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"
+
+# CORD Provisioner Dockerfile
+WORKDIR $GOPATH
+
+RUN apt-get update && \
+ apt-get install -y software-properties-common && \
+ apt-add-repository ppa:ansible/ansible && \
+ apt-get update -y -m && \
+ apt-get install -y git ansible
+
+RUN go get github.com/tools/godep
+ADD . $GOPATH/src/github.com/ciena/cord-provisioner
+
+WORKDIR $GOPATH/src/github.com/ciena/cord-provisioner
+RUN $GOPATH/bin/godep restore
+
+WORKDIR $GOPATH
+RUN go install github.com/ciena/cord-provisioner
+
+ENTRYPOINT ["$GOPATH/bin/cord-provisioner"]
diff --git a/provisioner/Godeps/Godeps.json b/provisioner/Godeps/Godeps.json
new file mode 100644
index 0000000..a68b4ef
--- /dev/null
+++ b/provisioner/Godeps/Godeps.json
@@ -0,0 +1,22 @@
+{
+ "ImportPath": "github.com/ciena/provisioner",
+ "GoVersion": "go1.6",
+ "GodepVersion": "v72",
+ "Deps": [
+ {
+ "ImportPath": "github.com/gorilla/context",
+ "Comment": "v1.1-4-gaed02d1",
+ "Rev": "aed02d124ae4a0e94fea4541c8effd05bf0c8296"
+ },
+ {
+ "ImportPath": "github.com/gorilla/mux",
+ "Comment": "v1.1-9-gbd09be0",
+ "Rev": "bd09be08ed4377796d312df0a45314e11b8f5dc1"
+ },
+ {
+ "ImportPath": "github.com/kelseyhightower/envconfig",
+ "Comment": "1.1.0-17-g91921eb",
+ "Rev": "91921eb4cf999321cdbeebdba5a03555800d493b"
+ }
+ ]
+}
diff --git a/provisioner/dispatcher.go b/provisioner/dispatcher.go
new file mode 100644
index 0000000..44e16e0
--- /dev/null
+++ b/provisioner/dispatcher.go
@@ -0,0 +1,137 @@
+package main
+
+import (
+ "log"
+ "os/exec"
+)
+
+type WorkRequest struct {
+ Info *RequestInfo
+ Script string
+ Role string
+}
+
+type Worker struct {
+ ID int
+ Work chan WorkRequest
+ StatusChan chan StatusMsg
+ WorkerQueue chan chan WorkRequest
+ QuitChan chan bool
+}
+
+type StatusMsg struct {
+ Request *WorkRequest
+ Worker int
+ Status TaskStatus
+ Message string
+}
+
+func NewWorker(id int, workerQueue chan chan WorkRequest, statusChan chan StatusMsg) Worker {
+ // Create, and return the worker.
+ worker := Worker{
+ ID: id,
+ Work: make(chan WorkRequest),
+ StatusChan: statusChan,
+ WorkerQueue: workerQueue,
+ QuitChan: make(chan bool)}
+
+ return worker
+}
+
+func (w *Worker) Start() {
+ go func() {
+ for {
+ // Add ourselves into the worker queue.
+ w.WorkerQueue <- w.Work
+
+ select {
+ case work := <-w.Work:
+ // Receive a work request.
+ w.StatusChan <- StatusMsg{&work, w.ID, Running, ""}
+ err := exec.Command(work.Script, work.Info.Id, work.Info.Name,
+ work.Info.Ip, work.Info.Mac, work.Role).Run()
+ if err != nil {
+ w.StatusChan <- StatusMsg{&work, w.ID, Failed, err.Error()}
+ } else {
+ w.StatusChan <- StatusMsg{&work, w.ID, Complete, ""}
+ }
+ case <-w.QuitChan:
+ // We have been asked to stop.
+ log.Printf("worker%d stopping\n", w.ID)
+ return
+ }
+ }
+ }()
+}
+
+func (w *Worker) Stop() {
+ go func() {
+ w.QuitChan <- true
+ }()
+}
+
+type Dispatcher struct {
+ Storage Storage
+ WorkQueue chan WorkRequest
+ WorkerQueue chan chan WorkRequest
+ StatusChan chan StatusMsg
+ QuitChan chan bool
+ NumWorkers int
+}
+
+func NewDispatcher(numWorkers int, storage Storage) *Dispatcher {
+ d := Dispatcher{
+ Storage: storage,
+ WorkQueue: make(chan WorkRequest, 100),
+ StatusChan: make(chan StatusMsg, 100),
+ NumWorkers: numWorkers,
+ WorkerQueue: make(chan chan WorkRequest, numWorkers),
+ QuitChan: make(chan bool),
+ }
+
+ return &d
+}
+
+func (d *Dispatcher) Dispatch(info *RequestInfo, role string) error {
+ d.WorkQueue <- WorkRequest{
+ Info: info,
+ Role: role,
+ }
+ return nil
+}
+
+func (d *Dispatcher) Start() {
+ // Now, create all of our workers.
+ for i := 0; i < d.NumWorkers; i++ {
+ log.Printf("Creating worker %d", i)
+ worker := NewWorker(i, d.WorkerQueue, d.StatusChan)
+ worker.Start()
+ }
+
+ go func() {
+ for {
+ select {
+ case work := <-d.WorkQueue:
+ log.Println("Received work requeust")
+ go func() {
+ d.StatusChan <- StatusMsg{&work, -1, Pending, ""}
+ worker := <-d.WorkerQueue
+
+ log.Println("Dispatching work request")
+ worker <- work
+ }()
+ case update := <-d.StatusChan:
+ d.Storage.Put(update.Request.Info.Id, update)
+ case <-d.QuitChan:
+ log.Println("[info] Stopping dispatcher")
+ return
+ }
+ }
+ }()
+}
+
+func (d *Dispatcher) Stop() {
+ go func() {
+ d.QuitChan <- true
+ }()
+}
diff --git a/provisioner/handlers.go b/provisioner/handlers.go
new file mode 100644
index 0000000..b915674
--- /dev/null
+++ b/provisioner/handlers.go
@@ -0,0 +1,105 @@
+package main
+
+import (
+ "bufio"
+ "encoding/json"
+ "github.com/gorilla/mux"
+ "log"
+ "net/http"
+ "strings"
+)
+
+type RequestInfo struct {
+ Id string
+ Name string
+ Ip string
+ Mac string
+}
+
+func (c *Context) GetRole(info *RequestInfo) (string, error) {
+ if c.config.RoleSelectorURL == "" {
+ return c.config.DefaultRole, nil
+ }
+ r, err := http.Get(c.config.RoleSelectorURL)
+ if err != nil {
+ return "", err
+ }
+
+ s := bufio.NewScanner(r.Body)
+ defer r.Body.Close()
+ role := strings.TrimSpace(s.Text())
+ if role == "" {
+ return c.config.DefaultRole, nil
+ }
+ return role, nil
+}
+
+func (c *Context) validateData(info *RequestInfo) bool {
+ if strings.TrimSpace(info.Id) == "" ||
+ strings.TrimSpace(info.Name) == "" ||
+ strings.TrimSpace(info.Ip) == "" ||
+ strings.TrimSpace(info.Mac) == "" {
+ return false
+ }
+ return true
+}
+
+func (c *Context) ProvisionRequestHandler(w http.ResponseWriter, r *http.Request) {
+ var info RequestInfo
+ decoder := json.NewDecoder(r.Body)
+ defer r.Body.Close()
+ if err := decoder.Decode(&info); err != nil || !c.validateData(&info) {
+ log.Printf("ERROR: %v", err)
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+
+ log.Printf("GOT: %v", info)
+
+ role, err := c.GetRole(&info)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ err = c.dispatcher.Dispatch(&info, role)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusAccepted)
+}
+
+func (c *Context) ListRequestsHandler(w http.ResponseWriter, r *http.Request) {
+ list, err := c.storage.List()
+ bytes, err := json.Marshal(list)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Write(bytes)
+}
+
+func (c *Context) QueryStatusHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ id, ok := vars["nodeid"]
+ if !ok || strings.TrimSpace(id) == "" {
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+ s, err := c.storage.Get(id)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if s == nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+ bytes, err := json.Marshal(s)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Write(bytes)
+}
diff --git a/provisioner/provisioner.go b/provisioner/provisioner.go
new file mode 100644
index 0000000..aeb237a
--- /dev/null
+++ b/provisioner/provisioner.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+ "fmt"
+ "github.com/gorilla/mux"
+ "github.com/kelseyhightower/envconfig"
+ "log"
+ "net/http"
+)
+
+type Config struct {
+ Port int `default:"4243"`
+ Listen string `default:"0.0.0.0"`
+ RoleSelectorURL string `default:"" envconfig:"role_selector_url"`
+ DefaultRole string `default:"compute-node" envconfig:"default_role"`
+ Script string `default:"do-ansible"`
+}
+
+type Context struct {
+ config Config
+ storage Storage
+ workers []Worker
+ dispatcher *Dispatcher
+}
+
+func main() {
+ context := &Context{}
+
+ err := envconfig.Process("PROVISION", &(context.config))
+ if err != nil {
+ log.Fatalf("[error] Unable to parse configuration options : %s", err)
+ }
+
+ log.Printf(`Configuration:
+ Listen: %s
+ Port: %d
+ RoleSelectorURL: %s
+ DefaultRole: %s`,
+ context.config.Listen, context.config.Port, context.config.RoleSelectorURL, context.config.DefaultRole)
+
+ context.storage = NewMemoryStorage()
+
+ router := mux.NewRouter()
+ router.HandleFunc("/provision/", context.ProvisionRequestHandler).Methods("POST")
+ router.HandleFunc("/provision/", context.ListRequestsHandler).Methods("GET")
+ router.HandleFunc("/provision/{nodeid}", context.QueryStatusHandler).Methods("GET")
+ http.Handle("/", router)
+
+ // Start the dispatcher and workers
+ context.dispatcher = NewDispatcher(5, context.storage)
+ context.dispatcher.Start()
+
+ http.ListenAndServe(fmt.Sprintf("%s:%d", context.config.Listen, context.config.Port), nil)
+}
diff --git a/provisioner/storage.go b/provisioner/storage.go
new file mode 100644
index 0000000..db64df2
--- /dev/null
+++ b/provisioner/storage.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+ "log"
+)
+
+type Storage interface {
+ Put(id string, update StatusMsg) error
+ Get(id string) (*StatusMsg, error)
+ List() ([]StatusMsg, error)
+}
+
+type MemoryStorage struct {
+ Data map[string]StatusMsg
+}
+
+func NewMemoryStorage() *MemoryStorage {
+ return &MemoryStorage{
+ Data: make(map[string]StatusMsg),
+ }
+}
+
+func (s *MemoryStorage) Put(id string, update StatusMsg) error {
+ s.Data[id] = update
+ log.Printf("%s : %s", id, update.Status.String())
+ return nil
+}
+
+func (s *MemoryStorage) Get(id string) (*StatusMsg, error) {
+ m, ok := s.Data[id]
+ if !ok {
+ return nil, nil
+ }
+ return &m, nil
+}
+
+func (s *MemoryStorage) List() ([]StatusMsg, error) {
+ r := make([]StatusMsg, len(s.Data))
+ i := 0
+ for _, v := range s.Data {
+ r[i] = v
+ }
+ return r, nil
+}
diff --git a/provisioner/task.go b/provisioner/task.go
new file mode 100644
index 0000000..4017268
--- /dev/null
+++ b/provisioner/task.go
@@ -0,0 +1,38 @@
+package main
+
+type TaskStatus uint8
+
+const (
+ Pending TaskStatus = iota
+ Running
+ Complete
+ Failed
+)
+
+func (s TaskStatus) String() string {
+ switch s {
+ case Pending:
+ return "PENDING"
+ case Running:
+ return "RUNNING"
+ case Complete:
+ return "COMPLETE"
+ case Failed:
+ return "FAILED"
+ }
+ return "INVALID TASK STATUS"
+}
+
+type Task struct {
+ nodeId string
+ status TaskStatus
+}
+
+type TaskQueueEntry struct {
+ previous *TaskQueueEntry
+ next *TaskQueueEntry
+ task *Task
+}
+
+type TaskQueue struct {
+}