github.com/jbking/gohan@v0.0.0-20151217002006-b41ccf1c2a96/server/server.go (about)

     1  // Copyright (C) 2015 NTT Innovation Institute, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    12  // implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package server
    17  
    18  import (
    19  	"fmt"
    20  	"net/http"
    21  	"os"
    22  	"os/signal"
    23  	"path"
    24  	"path/filepath"
    25  	"strings"
    26  
    27  	"github.com/cloudwan/gohan/cloud"
    28  	"github.com/cloudwan/gohan/db"
    29  	l "github.com/cloudwan/gohan/log"
    30  	"github.com/cloudwan/gohan/schema"
    31  	"github.com/cloudwan/gohan/server/middleware"
    32  	"github.com/cloudwan/gohan/sync"
    33  	"github.com/cloudwan/gohan/sync/etcd"
    34  	"github.com/cloudwan/gohan/util"
    35  	"github.com/go-martini/martini"
    36  	//Import gohan extension buildins
    37  )
    38  
    39  type tls struct {
    40  	CertFile string
    41  	KeyFile  string
    42  }
    43  
    44  //Server is a struct for GohanAPIServer
    45  type Server struct {
    46  	address          string
    47  	tls              *tls
    48  	documentRoot     string
    49  	db               db.DB
    50  	sync             sync.Sync
    51  	running          bool
    52  	martini          *martini.ClassicMartini
    53  	keystoneIdentity middleware.IdentityService
    54  }
    55  
    56  func (server *Server) mapRoutes() {
    57  	config := util.GetConfig()
    58  	schemaManager := schema.GetManager()
    59  	MapNamespacesRoutes(server.martini)
    60  	MapRouteBySchemas(server, server.db)
    61  
    62  	tx, err := server.db.Begin()
    63  	if err != nil {
    64  		log.Fatal(err)
    65  	}
    66  	defer tx.Close()
    67  	coreSchema, _ := schemaManager.Schema("schema")
    68  	if coreSchema == nil {
    69  		log.Fatal("Gohan core schema not found")
    70  		return
    71  	}
    72  
    73  	policySchema, _ := schemaManager.Schema("policy")
    74  	policyList, _, err := tx.List(policySchema, nil, nil)
    75  	if err != nil {
    76  		log.Info(err.Error())
    77  	}
    78  	schemaManager.LoadPolicies(policyList)
    79  
    80  	extensionSchema, _ := schemaManager.Schema("extension")
    81  	extensionList, _, err := tx.List(extensionSchema, nil, nil)
    82  	if err != nil {
    83  		log.Info(err.Error())
    84  	}
    85  	schemaManager.LoadExtensions(extensionList)
    86  
    87  	namespaceSchema, _ := schemaManager.Schema("namespace")
    88  	if namespaceSchema == nil {
    89  		log.Error("No gohan schema. Disabling schema editing mode")
    90  		return
    91  	}
    92  	namespaceList, _, err := tx.List(namespaceSchema, nil, nil)
    93  	if err != nil {
    94  		log.Info(err.Error())
    95  	}
    96  	err = tx.Commit()
    97  	if err != nil {
    98  		log.Info(err.Error())
    99  	}
   100  	schemaManager.LoadNamespaces(namespaceList)
   101  
   102  	if config.GetBool("keystone/fake", false) {
   103  		middleware.FakeKeystone(server.martini)
   104  	}
   105  }
   106  
   107  func (server *Server) addOptionsRoute() {
   108  	server.martini.AddRoute("OPTIONS", ".*", func(w http.ResponseWriter, r *http.Request) {
   109  		w.WriteHeader(http.StatusOK)
   110  	})
   111  }
   112  
   113  func (server *Server) resetRouter() {
   114  	router := martini.NewRouter()
   115  	server.martini.Router = router
   116  	server.martini.MapTo(router, (*martini.Routes)(nil))
   117  	server.martini.Action(router.Handle)
   118  	server.addOptionsRoute()
   119  }
   120  
   121  func (server *Server) initDB() error {
   122  	return db.InitDBWithSchemas(server.getDatabaseConfig())
   123  }
   124  
   125  func (server *Server) connectDB() error {
   126  	dbType, dbConnection, _, _ := server.getDatabaseConfig()
   127  	dbConn, err := db.ConnectDB(dbType, dbConnection)
   128  	server.db = &DbSyncWrapper{dbConn}
   129  	return err
   130  }
   131  
   132  func (server *Server) getDatabaseConfig() (string, string, bool, bool) {
   133  	config := util.GetConfig()
   134  	databaseType := config.GetString("database/type", "sqlite3")
   135  	if databaseType == "json" || databaseType == "yaml" {
   136  		log.Fatal("json or yaml isn't supported as main db backend")
   137  	}
   138  	databaseConnection := config.GetString("database/connection", "")
   139  	if databaseConnection == "" {
   140  		log.Fatal("no database connection specified in the configuraion file.")
   141  	}
   142  	databaseDropOnCreate := config.GetBool("database/drop_on_create", false)
   143  	databaseCascade := config.GetBool("database/cascade_delete", false)
   144  	return databaseType, databaseConnection, databaseDropOnCreate, databaseCascade
   145  }
   146  
   147  //NewServer returns new GohanAPIServer
   148  func NewServer(configFile string) (*Server, error) {
   149  	manager := schema.GetManager()
   150  	config := util.GetConfig()
   151  	err := config.ReadConfig(configFile)
   152  	err = os.Chdir(path.Dir(configFile))
   153  	if err != nil {
   154  		return nil, fmt.Errorf("Config load error: %s", err)
   155  	}
   156  	err = l.SetUpLogging(config)
   157  	if err != nil {
   158  		return nil, fmt.Errorf("Logging setup error: %s", err)
   159  	}
   160  	log.Info("logging initialized")
   161  
   162  	server := &Server{}
   163  
   164  	m := martini.Classic()
   165  	m.Handlers()
   166  	m.Use(middleware.Logging())
   167  	m.Use(martini.Recovery())
   168  	m.Use(middleware.JSONURLs())
   169  	m.Use(middleware.WithContext())
   170  
   171  	server.martini = m
   172  
   173  	port := os.Getenv("PORT")
   174  
   175  	if port == "" {
   176  		port = "9443"
   177  	}
   178  
   179  	setupEditor(server)
   180  
   181  	server.address = config.GetString("address", ":"+port)
   182  	if config.GetBool("tls/enabled", false) {
   183  		log.Info("TLS enabled")
   184  		server.tls = &tls{
   185  			KeyFile:  config.GetString("tls/key_file", "./etc/key.pem"),
   186  			CertFile: config.GetString("tls/cert_file", "./etc/cert.pem"),
   187  		}
   188  	}
   189  
   190  	server.connectDB()
   191  
   192  	schemaFiles := config.GetStringList("schemas", nil)
   193  	if schemaFiles == nil {
   194  		log.Fatal("No schema specified in configuraion")
   195  	} else {
   196  		err = manager.LoadSchemasFromFiles(schemaFiles...)
   197  		if err != nil {
   198  			return nil, fmt.Errorf("invalid schema: %s", err)
   199  		}
   200  	}
   201  	server.initDB()
   202  
   203  	etcdServers := config.GetStringList("etcd", nil)
   204  	if etcdServers != nil {
   205  		log.Info("etcd servers: %s", etcdServers)
   206  		server.sync = etcd.NewSync(etcdServers)
   207  	}
   208  
   209  	if config.GetList("database/initial_data", nil) != nil {
   210  		initialDataList := config.GetList("database/initial_data", nil)
   211  		for _, initialData := range initialDataList {
   212  			initialDataConfig := initialData.(map[string]interface{})
   213  			inType := initialDataConfig["type"].(string)
   214  			inConnection := initialDataConfig["connection"].(string)
   215  			log.Info("Importing data from %s ...", inConnection)
   216  			inDB, err := db.ConnectDB(inType, inConnection)
   217  			if err != nil {
   218  				log.Fatal(err)
   219  			}
   220  			db.CopyDBResources(inDB, server.db)
   221  		}
   222  	}
   223  
   224  	if config.GetBool("keystone/use_keystone", false) {
   225  		//TODO remove this
   226  		if config.GetBool("keystone/fake", false) {
   227  			server.keystoneIdentity = &middleware.FakeIdentity{}
   228  			//TODO(marcin) requests to fake server also get authenticated
   229  			//             we need a separate routing Group
   230  			log.Info("Debug Mode with Fake Keystone Server")
   231  		} else {
   232  			log.Info("Keystone backend server configured")
   233  			server.keystoneIdentity, err = cloud.NewKeystoneIdentity(
   234  				config.GetString("keystone/auth_url", "http://localhost:35357/v3"),
   235  				config.GetString("keystone/user_name", "admin"),
   236  				config.GetString("keystone/password", "password"),
   237  				config.GetString("keystone/domain_name", "Default"),
   238  				config.GetString("keystone/tenant_name", "admin"),
   239  				config.GetString("keystone/version", ""),
   240  			)
   241  			if err != nil {
   242  				log.Fatal(err)
   243  			}
   244  		}
   245  		m.MapTo(server.keystoneIdentity, (*middleware.IdentityService)(nil))
   246  		m.Use(middleware.Authentication())
   247  		//m.Use(Authorization())
   248  	}
   249  
   250  	if err != nil {
   251  		return nil, fmt.Errorf("invalid base dir: %s", err)
   252  	}
   253  
   254  	server.addOptionsRoute()
   255  	cors := config.GetString("cors", "")
   256  	if cors != "" {
   257  		log.Info("Enabling CORS for %s", cors)
   258  		if cors == "*" {
   259  			log.Warning("cors for * have security issue")
   260  		}
   261  		server.martini.Use(func(rw http.ResponseWriter, r *http.Request) {
   262  			rw.Header().Add("Access-Control-Allow-Origin", cors)
   263  			rw.Header().Add("Access-Control-Allow-Headers", "X-Auth-Token, Content-Type")
   264  			rw.Header().Add("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE")
   265  		})
   266  	}
   267  
   268  	documentRoot := config.GetString("document_root", "./")
   269  	log.Info("Static file serving from %s", documentRoot)
   270  	documentRootABS, err := filepath.Abs(documentRoot)
   271  	server.martini.Use(martini.Static(documentRootABS))
   272  
   273  	server.mapRoutes()
   274  	return server, nil
   275  }
   276  
   277  //Start starts GohanAPIServer
   278  func (server *Server) Start() (err error) {
   279  	if server.tls != nil {
   280  		err = http.ListenAndServeTLS(server.address, server.tls.CertFile, server.tls.KeyFile, server.martini)
   281  	} else {
   282  		err = http.ListenAndServe(server.address, server.martini)
   283  	}
   284  	return err
   285  }
   286  
   287  //Stop stops GohanAPIServer
   288  func (server *Server) Stop() {
   289  	server.running = false
   290  	if server.sync != nil {
   291  		stopSyncProcess(server)
   292  		stopStateUpdatingProcess(server)
   293  		stopSyncWatchProcess(server)
   294  	}
   295  	stopAMQPProcess(server)
   296  	stopSNMPProcess(server)
   297  	stopCRONProcess(server)
   298  }
   299  
   300  //RunServer runs gohan api server
   301  func RunServer(configFile string) {
   302  	c := make(chan os.Signal, 1)
   303  	signal.Notify(c, os.Interrupt)
   304  	server, err := NewServer(configFile)
   305  	if err != nil {
   306  		log.Fatal(err)
   307  	}
   308  	log.Info("Gohan no jikan desuyo (It's time for dinner!) ")
   309  	log.Info("Starting Gohan Server...")
   310  	address := server.address
   311  	if strings.HasPrefix(address, ":") {
   312  		address = "localhost" + address
   313  	}
   314  	protocol := "http"
   315  	if server.tls != nil {
   316  		protocol = "https"
   317  	}
   318  	log.Info("    API Server %s://%s/", protocol, address)
   319  	log.Info("    Web UI %s://%s/webui/", protocol, address)
   320  	go func() {
   321  		for _ = range c {
   322  			log.Info("Stopping the server...")
   323  			log.Info("Tearing down...")
   324  			server.Stop()
   325  			log.Fatal("Finished - bye bye.  ;-)")
   326  			os.Exit(1)
   327  		}
   328  	}()
   329  	server.running = true
   330  
   331  	if server.sync != nil {
   332  		startSyncProcess(server)
   333  		startStateUpdatingProcess(server)
   334  		startSyncWatchProcess(server)
   335  	}
   336  	startAMQPProcess(server)
   337  	startSNMPProcess(server)
   338  	startCRONProcess(server)
   339  	log.Error(fmt.Sprintf("Error in Serve: %s", server.Start()))
   340  }
   341  
   342  func startAMQPNotificationProcess(server *Server) {
   343  
   344  }