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 }