Add checks for the number of (atomic) actions in a sequence

* Part of resolution for Issue #1274.

Add checks for the number of (atomic) actions in a sequence.
Also check for recursion.
Remove obsolete tests.
Add relevant tests in independent sequences test file. 
Use FullyQualifiedEntityName instead of strings for components.
Added JSON serializer and corresponding changes to Exec serdes; this is a dual serdes for FullyQualifiedEntityName to allow for Json Object and JsString deserialization.
Migration tests from old sequence to new sequence implementation.

diff --git a/tests/src/common/Wsk.scala b/tests/src/common/Wsk.scala
index c125a71..8dbe6b1 100644
--- a/tests/src/common/Wsk.scala
+++ b/tests/src/common/Wsk.scala
@@ -794,6 +794,16 @@
     def baseCommand = {
         Buffer(WhiskProperties.python, new File(binDir, binaryName).toString)
     }
+
+    /**
+     * returns user given the auth key
+     */
+    def getUser(authKey: String): String = {
+        val wskadmin = new RunWskAdminCmd {}
+        val user = wskadmin.cli(Seq("user", "whois", authKey)).stdout.trim
+        assert(!user.contains("Subject id is not recognized"), s"failed to retrieve user from authkey '$authKey'")
+        user
+    }
 }
 
 trait RunWskAdminCmd extends RunWskCmd {
diff --git a/tests/src/system/basic/WskActionSequenceTests.scala b/tests/src/system/basic/WskActionSequenceTests.scala
index 8147d34..75d980e 100644
--- a/tests/src/system/basic/WskActionSequenceTests.scala
+++ b/tests/src/system/basic/WskActionSequenceTests.scala
@@ -28,6 +28,7 @@
 import common.TestHelpers
 import common.TestUtils
 import common.Wsk
+import common.WskAdmin
 import common.WskProps
 import common.WskTestHelpers
 import spray.json.DefaultJsonProtocol.IntJsonFormat
@@ -46,14 +47,14 @@
     implicit val wskprops = WskProps()
     val wsk = new Wsk
     val allowedActionDuration = 120 seconds
-    val guestNamespace = wskprops.namespace
+    val defaultNamespace = wskprops.namespace
+    val user = WskAdmin.getUser(wskprops.authKey)
 
     behavior of "Wsk Action Sequence"
 
     it should "invoke a blocking action and get only the result" in withAssetCleaner(wskprops) {
         (wp, assetHelper) =>
             val name = "sequence"
-
             val actions = Seq("split", "sort", "head", "cat")
             for (actionName <- actions) {
                 val file = TestUtils.getTestActionFilename(s"$actionName.js")
@@ -96,8 +97,8 @@
             val packageName = "samples"
             val helloName = "hello"
             val catName = "cat"
-            val fullHelloActionName = s"/$guestNamespace/$packageName/$helloName"
-            val fullCatActionName = s"/$guestNamespace/$packageName/$catName"
+            val fullHelloActionName = s"/$defaultNamespace/$packageName/$helloName"
+            val fullCatActionName = s"/$defaultNamespace/$packageName/$catName"
 
             assetHelper.withCleaner(wsk.pkg, packageName) {
                 (pkg, _) => pkg.create(packageName, shared = Some(true))(wp)
@@ -116,8 +117,8 @@
             val artifacts = s"$fullHelloActionName,$fullCatActionName"
             val kindValue = JsString("sequence")
             val compValue = JsArray(
-                JsString(fullHelloActionName),
-                JsString(fullCatActionName))
+                JsString(resolveDefaultNamespace(fullHelloActionName)),
+                JsString(resolveDefaultNamespace(fullCatActionName)))
 
             assetHelper.withCleaner(wsk.action, name) {
                 (action, _) => action.create(name, Some(artifacts), kind = Some("sequence"))
@@ -129,4 +130,5 @@
             wsk.parseJsonString(stdout).fields("exec").asJsObject.fields("kind") shouldBe kindValue
     }
 
+    private def resolveDefaultNamespace(actionName: String) = actionName.replace("/_/", s"/$user/")
 }
diff --git a/tests/src/whisk/core/cli/test/SequenceMigrationTests.scala b/tests/src/whisk/core/cli/test/SequenceMigrationTests.scala
new file mode 100644
index 0000000..ea4921e
--- /dev/null
+++ b/tests/src/whisk/core/cli/test/SequenceMigrationTests.scala
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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 whisk.core.cli.test
+
+
+import java.util.Date
+import scala.concurrent.duration.DurationInt
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+import common.JsHelpers
+import common.TestHelpers
+import common.TestUtils
+import common.Wsk
+import common.WskAdmin
+import common.WskProps
+import common.WskTestHelpers
+import spray.json._
+import spray.json.DefaultJsonProtocol.StringJsonFormat
+import spray.testkit.ScalatestRouteTest
+import whisk.core.WhiskConfig
+import whisk.core.entity._
+import whisk.core.database.test.DbUtils
+import org.scalatest.BeforeAndAfter
+
+import scala.language.postfixOps
+
+/**
+ * Tests that "old-style" sequences can be invoked
+ */
+
+@RunWith(classOf[JUnitRunner])
+class SequenceMigrationTests
+    extends TestHelpers
+    with BeforeAndAfter
+    with DbUtils
+    with JsHelpers
+    with ScalatestRouteTest
+    with WskTestHelpers {
+
+    implicit val actorSystem = system
+
+    implicit val wskprops = WskProps()
+    val wsk = new Wsk
+    val whiskConfig = new WhiskConfig(WhiskEntityStore.requiredProperties)
+    // handle on the entity datastore
+    val entityStore = WhiskEntityStore.datastore(whiskConfig)
+    val user = WskAdmin.getUser(wskprops.authKey)
+    val allowedActionDuration = 120 seconds
+
+    behavior of "Sequence Migration"
+
+    it should "invoke an old-style sequence and get the result" in withAssetCleaner(wskprops) {
+        (wp, assetHelper) =>
+            // create entities to insert in the entity store
+            val echo = "echo.json"
+            val wc = "word_count.json"
+            val seq_echo_wc = "seq_echo_word_count.json"   // old-style sequence
+            val entities = Seq(echo, wc, seq_echo_wc)
+            implicit val tid = transid() // needed for put db below
+            for (entity <- entities) {
+                // read json file and add the appropriate namespace
+                val jsonFile = TestUtils.getTestActionFilename(s"$entity")
+                val source = scala.io.Source.fromFile(jsonFile)
+                val jsonString = try source.mkString finally source.close()
+                val entityJson = jsonString.parseJson.asJsObject
+                // add default namespace (i.e., user) to the json object
+                val entityJsonWithNamespace = JsObject(entityJson.fields + ("namespace" -> JsString(user)))
+                val wskEntity = entityJsonWithNamespace.convertTo[WhiskAction]
+                put(entityStore, wskEntity)
+            }
+            // invoke sequence
+            val seqName = "seq_echo_word_count"
+            val now = "it is now " + new Date()
+            val run = wsk.action.invoke(seqName, Map("payload" -> now.mkString("\n").toJson))
+            withActivation(wsk.activation, run, totalWait = allowedActionDuration) {
+                activation =>
+                    val result = activation.response.result.get
+                    result.fields.get("count") shouldBe Some(JsNumber(now.split(" ").size))
+            }
+    }
+
+    after {
+        cleanup()  // cleanup entities from db
+    }
+}
diff --git a/tests/src/whisk/core/controller/test/ActionsApiTests.scala b/tests/src/whisk/core/controller/test/ActionsApiTests.scala
index fb67d6c..1c041a8 100644
--- a/tests/src/whisk/core/controller/test/ActionsApiTests.scala
+++ b/tests/src/whisk/core/controller/test/ActionsApiTests.scala
@@ -58,10 +58,9 @@
 import whisk.core.entity.WhiskActivation
 import whisk.core.entity.WhiskAuth
 import java.time.Instant
-import whisk.core.entity.SequenceExec
-import whisk.core.entity.Pipecode
 import akka.event.Logging.InfoLevel
 import whisk.core.entity.WhiskTrigger
+import whisk.core.entity.FullyQualifiedEntityName
 
 /**
  * Tests Actions API.
@@ -311,94 +310,13 @@
         }
     }
 
-    private def seqParameters(seq: Vector[String]) = Parameters("_actions", seq.toJson)
+    private def seqParameters(seq: Vector[FullyQualifiedEntityName]) = Parameters("_actions", seq.toJson)
 
-    it should "create an action sequence" in {
-        implicit val tid = transid()
-        val sequence = Vector("a", "b")
-        val action = WhiskAction(namespace, aname, Exec.sequence(sequence))
-        val content = WhiskActionPut(Some(action.exec))
-
-        // create an action sequence
-        Put(s"$collectionPath/${action.name}", content) ~> sealRoute(routes(creds)) ~> check {
-            deleteAction(action.docid)
-            status should be(OK)
-            val response = responseAs[WhiskAction]
-            response.exec shouldBe a[SequenceExec]
-            response.exec.kind should be(Exec.SEQUENCE)
-            val seq = response.exec.asInstanceOf[SequenceExec]
-            seq.code should be(Pipecode.code)
-            seq.components should be(sequence)
-            response.parameters shouldBe seqParameters(sequence)
-        }
-    }
-
-    it should "create an action sequence ignoring parameters" in {
-        implicit val tid = transid()
-        val sequence = Vector("a", "b")
-        val action = WhiskAction(namespace, aname, Exec.sequence(sequence))
-        val content = WhiskActionPut(Some(action.exec), parameters = Some(Parameters("x", "X")))
-
-        // create an action sequence
-        Put(s"$collectionPath/${action.name}", content) ~> sealRoute(routes(creds)) ~> check {
-            deleteAction(action.docid)
-            status should be(OK)
-            val response = responseAs[WhiskAction]
-            response.exec shouldBe a[SequenceExec]
-            response.exec.kind should be(Exec.SEQUENCE)
-            val seq = response.exec.asInstanceOf[SequenceExec]
-            seq.code should be(Pipecode.code)
-            seq.components should be(sequence)
-            response.parameters shouldBe seqParameters(sequence)
-        }
-    }
-
-    it should "update an action sequence with a new sequence" in {
-        implicit val tid = transid()
-        val sequence = Vector("a", "b")
-        val newSequence = Vector("c", "d")
-        val action = WhiskAction(namespace, aname, Exec.sequence(sequence), seqParameters(sequence))
-        val content = WhiskActionPut(Some(Exec.sequence(newSequence)))
-        put(entityStore, action, false)
-
-        // create an action sequence
-        Put(s"$collectionPath/${action.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check {
-            deleteAction(action.docid)
-            status should be(OK)
-            val response = responseAs[WhiskAction]
-            response.exec shouldBe a[SequenceExec]
-            response.exec.kind should be(Exec.SEQUENCE)
-            val seq = response.exec.asInstanceOf[SequenceExec]
-            seq.code should be(Pipecode.code)
-            seq.components should be(newSequence)
-            response.parameters shouldBe seqParameters(newSequence)
-        }
-    }
-
-    it should "update an action sequence ignoring parameters" in {
-        implicit val tid = transid()
-        val sequence = Vector("a", "b")
-        val action = WhiskAction(namespace, aname, Exec.sequence(sequence), seqParameters(sequence))
-        val content = WhiskActionPut(parameters = Some(Parameters("a", "A")))
-        put(entityStore, action, false)
-
-        // create an action sequence
-        Put(s"$collectionPath/${action.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check {
-            deleteAction(action.docid)
-            status should be(OK)
-            val response = responseAs[WhiskAction]
-            response.exec shouldBe a[SequenceExec]
-            response.exec.kind should be(Exec.SEQUENCE)
-            val seq = response.exec.asInstanceOf[SequenceExec]
-            seq.code should be(Pipecode.code)
-            seq.components should be(sequence)
-            response.parameters shouldBe seqParameters(sequence)
-        }
-    }
-
+    // this test is sneaky; the installation of the sequence is done directly in the db
+    // and api checks are skipped
     it should "reset parameters when changing sequence action to non sequence" in {
         implicit val tid = transid()
-        val sequence = Vector("a", "b")
+        val sequence = Vector("x/a", "x/b").map(stringToFullyQualifiedName(_))
         val action = WhiskAction(namespace, aname, Exec.sequence(sequence), seqParameters(sequence))
         val content = WhiskActionPut(Some(Exec.js("")))
         put(entityStore, action, false)
@@ -413,9 +331,11 @@
         }
     }
 
+    // this test is sneaky; the installation of the sequence is done directly in the db
+    // and api checks are skipped
     it should "preserve new parameters when changing sequence action to non sequence" in {
         implicit val tid = transid()
-        val sequence = Vector("a", "b")
+        val sequence = Vector("x/a", "x/b").map(stringToFullyQualifiedName(_))
         val action = WhiskAction(namespace, aname, Exec.sequence(sequence), seqParameters(sequence))
         val content = WhiskActionPut(Some(Exec.js("")), parameters = Some(Parameters("a", "A")))
         put(entityStore, action, false)
diff --git a/tests/src/whisk/core/controller/test/ControllerTestCommon.scala b/tests/src/whisk/core/controller/test/ControllerTestCommon.scala
index 8c76838..0fd8226 100644
--- a/tests/src/whisk/core/controller/test/ControllerTestCommon.scala
+++ b/tests/src/whisk/core/controller/test/ControllerTestCommon.scala
@@ -27,6 +27,7 @@
 
 import akka.event.Logging.{ InfoLevel, LogLevel }
 import spray.http.BasicHttpCredentials
+import spray.json.JsString
 import spray.routing.HttpService
 import spray.testkit.ScalatestRouteTest
 import whisk.common.{ Logging, TransactionCounter, TransactionId }
@@ -39,6 +40,7 @@
 import whisk.core.entity._
 import whisk.core.loadBalancer.LoadBalancer
 
+
 protected trait ControllerTestCommon
     extends FlatSpec
     with BeforeAndAfter
@@ -124,6 +126,8 @@
         }, dbOpTimeout)
     }
 
+    def stringToFullyQualifiedName(s: String) = FullyQualifiedEntityName.serdes.read(JsString(s))
+
     object MakeName {
         @volatile var counter = 1
         def next(prefix: String = "test")(): EntityName = {
diff --git a/tests/src/whisk/core/controller/test/SequenceApiTests.scala b/tests/src/whisk/core/controller/test/SequenceApiTests.scala
new file mode 100644
index 0000000..0a20201
--- /dev/null
+++ b/tests/src/whisk/core/controller/test/SequenceApiTests.scala
@@ -0,0 +1,431 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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 whisk.core.controller.test
+
+import scala.concurrent.duration.DurationInt
+import scala.language.postfixOps
+import java.io.PrintStream
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import spray.http.StatusCodes._
+import spray.httpx.SprayJsonSupport._
+import spray.json.DefaultJsonProtocol._
+import spray.json._
+import whisk.common.TransactionId
+import whisk.core.controller.WhiskActionsApi
+import whisk.core.entity.AuthKey
+import whisk.core.entity.EntityName
+import whisk.core.entity.EntityPath
+import whisk.core.entity.Exec
+import whisk.core.entity.Subject
+import whisk.core.entity.WhiskAction
+import whisk.core.entity.WhiskActionPut
+import whisk.core.entity.WhiskAuth
+import whisk.core.entity.WhiskPackage
+import whisk.http.Messages._
+
+import akka.event.Logging.DebugLevel
+
+/**
+ * Tests Sequence API - stand-alone tests that require only the controller to be up
+ */
+@RunWith(classOf[JUnitRunner])
+class SequenceApiTests
+    extends ControllerTestCommon
+    with WhiskActionsApi {
+
+    behavior of "Sequence API"
+
+    val collectionPath = s"/${EntityPath.DEFAULT}/${collection.path}"
+    val creds = WhiskAuth(Subject(), AuthKey()).toIdentity
+    val namespace = EntityPath(creds.subject())
+    val defaultNamespace = EntityPath.DEFAULT
+    def aname() = MakeName.next("sequence_tests")
+    val allowedActionDuration = 120 seconds
+
+    // set logging level to debug
+    setVerbosity(DebugLevel)
+
+    it should "reject creation of sequence with more actions than allowed limit" in {
+        implicit val tid = transid()
+        val seqName = EntityName(s"${aname()}_toomanyactions")
+        // put the component action in the entity store so it's found
+        val component = WhiskAction(namespace, aname(), Exec.js("??"))
+        put(entityStore, component)
+        // create exec sequence that will violate max length
+        val limit = whiskConfig.actionSequenceLimit.toInt + 1 // one more than allowed
+        val sequence = for (i <- 1 to limit) yield stringToFullyQualifiedName(component.docid())
+        val content = WhiskActionPut(Some(Exec.sequence(sequence.toVector)))
+
+        // create an action sequence
+        Put(s"$collectionPath/${seqName.name}", content) ~> sealRoute(routes(creds)) ~> check {
+            status should be(BadRequest)
+            response.entity.toString should include(sequenceIsTooLong)
+        }
+    }
+
+    it should "reject creation of sequence with non-existent action" in {
+        implicit val tid = transid()
+        val seqName = EntityName(s"${aname()}_componentnotfound")
+        val bogus = s"${aname()}_bogus"
+        val bogusAction = s"/$namespace/$bogus"
+        val sequence = Vector(bogusAction).map(stringToFullyQualifiedName(_))
+        val content = WhiskActionPut(Some(Exec.sequence(sequence)))
+
+        // create an action sequence
+        Put(s"$collectionPath/${seqName.name}", content) ~> sealRoute(routes(creds)) ~> check {
+            status should be(BadRequest)
+            response.entity.toString should include(sequenceComponentNotFound)
+        }
+    }
+
+    it should "reject create sequence that points to itself" in {
+        implicit val tid = transid()
+        val seqName = s"${aname()}_cyclic"
+        val sSeq = makeSimpleSequence(seqName, namespace, Vector(seqName), false)
+
+        // create an action sequence
+        val content = WhiskActionPut(Some(sSeq.exec))
+        Put(s"$collectionPath/$seqName", content) ~> sealRoute(routes(creds)) ~> check {
+            status should be(BadRequest)
+            response.entity.toString should include(sequenceIsCyclic)
+        }
+    }
+
+    it should "reject create sequence that points to itself with many components" in {
+        implicit val tid = transid()
+
+        // put the action in the entity store so it's found
+        val component = WhiskAction(namespace, aname(), Exec.js("??"))
+        put(entityStore, component)
+
+        val seqName = s"${aname()}_cyclic"
+        val sSeq = makeSimpleSequence(seqName, namespace, Vector(component.name(), seqName, component.name()), false)
+
+        // create an action sequence
+        val content = WhiskActionPut(Some(sSeq.exec))
+        Put(s"$collectionPath/$seqName", content) ~> sealRoute(routes(creds)) ~> check {
+            status should be(BadRequest)
+            response.entity.toString should include(sequenceIsCyclic)
+        }
+    }
+
+    it should "reject update of sequence with cycle" in {
+        implicit val tid = transid()
+        val seqName = EntityName(s"${aname()}_cycle")
+        // put the component action in the entity store so it's found
+        val component = WhiskAction(namespace, aname(), Exec.js("??"))
+        put(entityStore, component)
+        // create valid exec sequence initially
+        val sequence = for (i <- 1 to 2) yield stringToFullyQualifiedName(component.docid())
+        val content = WhiskActionPut(Some(Exec.sequence(sequence.toVector)))
+
+        // create a valid action sequence first
+        Put(s"$collectionPath/${seqName.name}", content) ~> sealRoute(routes(creds)) ~> check {
+            status should be(OK)
+        }
+
+        // now create exec sequence with a self-reference
+        val seqNameWithNamespace = stringToFullyQualifiedName(s"/${namespace}/${seqName.name}")
+        val updatedSeq = sequence.updated(1, seqNameWithNamespace)
+        val updatedContent = WhiskActionPut(Some(Exec.sequence(updatedSeq.toVector)))
+
+        // update the sequence
+        Put(s"$collectionPath/${seqName.name}?overwrite=true", updatedContent) ~> sealRoute(routes(creds)) ~> check {
+            status should be(BadRequest)
+            response.entity.toString should include(sequenceIsCyclic)
+        }
+    }
+
+    it should "allow creation of sequence provided the number of actions is <= than allowed limit" in {
+        implicit val tid = transid()
+        val seqName = EntityName(s"${aname()}_normal")
+        // put the component action in the entity store so it's found
+        val component = WhiskAction(namespace, aname(), Exec.js("??"))
+        put(entityStore, component)
+        // create valid exec sequence
+        val limit = whiskConfig.actionSequenceLimit.toInt
+        val sequence = for (i <- 1 to limit) yield stringToFullyQualifiedName(component.docid())
+        val content = WhiskActionPut(Some(Exec.sequence(sequence.toVector)))
+
+        // create an action sequence
+        Put(s"$collectionPath/${seqName.name}", content) ~> sealRoute(routes(creds)) ~> check {
+            status should be(OK)
+        }
+    }
+
+    it should "allow creation of sequence with actions with package bindings" in {
+        implicit val tid = transid()
+        val seqName = EntityName(s"${aname()}_withbindings")
+
+        // create the package
+        val pkg = s"${aname()}_pkg"
+        val wp = WhiskPackage(namespace, EntityName(pkg), None, publish = true)
+        put(entityStore, wp)
+
+        // create binding to wp
+        val pkgWithBinding = s"${aname()}_pkgbinding"
+        val wpBinding = WhiskPackage(namespace, EntityName(pkgWithBinding), wp.bind)
+        put(entityStore, wpBinding)
+
+        // put the action in the entity store so it exists
+        val actionName = s"${aname()}_action"
+        val namespaceWithPkg = s"/$namespace/$pkg"
+        val action = WhiskAction(EntityPath(namespaceWithPkg), EntityName(actionName), Exec.js("??"))
+        put(entityStore, action)
+
+        // create sequence that refers to action with binding
+        val sequence = Vector(s"/$defaultNamespace/$pkgWithBinding/$actionName").map(stringToFullyQualifiedName(_))
+        val content = WhiskActionPut(Some(Exec.sequence(sequence)))
+
+        // create an action sequence
+        Put(s"$collectionPath/${seqName.name}", content) ~> sealRoute(routes(creds)) ~> check {
+            status should be(OK)
+            val response = responseAs[String]
+        }
+    }
+
+    it should "reject update of sequence with cycle through bindings" in {
+        implicit val tid = transid()
+        val seqName = EntityName(s"${aname()}_cycle_binding")
+
+        // put the action in the entity store so it's found
+        val component = WhiskAction(namespace, aname(), Exec.js("??"))
+        put(entityStore, component)
+        val sequence = for (i <- 1 to 2) yield stringToFullyQualifiedName(component.docid())
+
+        // create package
+        val pkg = s"${aname()}_pkg"
+        val wp = WhiskPackage(namespace, EntityName(pkg), None, publish = true)
+        put(entityStore, wp)
+
+        // create an action sequence
+        val namespaceWithPkg = EntityPath(s"/$namespace/$pkg")
+        val content = WhiskActionPut(Some(Exec.sequence(sequence.toVector)))
+        Put(s"$collectionPath/$pkg/${seqName.name}", content) ~> sealRoute(routes(creds)) ~> check {
+            status should be(OK)
+        }
+
+        // create binding
+        val pkgWithBinding = s"${aname()}_pkgbinding"
+        val wpBinding = WhiskPackage(namespace, EntityName(pkgWithBinding), wp.bind)
+        put(entityStore, wpBinding)
+
+        // now update the sequence to refer to itself through the binding
+        val seqNameWithBinding = stringToFullyQualifiedName(s"/$namespace/$pkgWithBinding/${seqName.name}")
+        val updatedSeq = sequence.updated(1, seqNameWithBinding)
+        val updatedContent = WhiskActionPut(Some(Exec.sequence(updatedSeq.toVector)))
+
+        // update the sequence
+        Put(s"$collectionPath/$pkg/${seqName.name}?overwrite=true", updatedContent) ~> sealRoute(routes(creds)) ~> check {
+            status should be(BadRequest)
+            response.entity.toString should include(sequenceIsCyclic)
+        }
+    }
+
+    it should "reject creation of a sequence with components that don't have at least namespace and action name" in {
+        implicit val tid = transid()
+        val content = """{"exec":{"kind":"sequence","code":"","components":["a","b"]}}""".parseJson.asJsObject
+
+        // create an action sequence
+        Put(s"$collectionPath/${aname()}", content) ~> sealRoute(routes(creds)) ~> check {
+            status should be(BadRequest)
+            response.entity.toString should include(malformedFullyQualifiedEntityName)
+        }
+    }
+
+    it should "reject update of a sequence with components that don't have at least namespace and action name" in {
+        implicit val tid = transid()
+        val name = s"${aname()}_bogus"
+        val bogusAct = WhiskAction(namespace, EntityName(name), Exec.js("??"))
+        // put the action in the entity store so it's found
+        put(entityStore, bogusAct)
+
+        val updatedContent = """{"exec":{"kind":"sequence","code":"","components":["a","b"]}}""".parseJson.asJsObject
+
+        // update an action sequence
+        Put(s"$collectionPath/$name?overwrite=true", updatedContent) ~> sealRoute(routes(creds)) ~> check {
+            deleteAction(bogusAct.docid)
+            status should be(BadRequest)
+            response.entity.toString should include(malformedFullyQualifiedEntityName)
+        }
+    }
+
+    it should "create a sequence of type s -> (x, x) where x is a sequence and correctly count the atomic actions" in {
+        implicit val tid = transid()
+        val actionCnt = 2
+
+        // make sequence x and install it in db
+        val xSeqName = s"${aname()}_x"
+        val components = for (i <- 1 to actionCnt) yield s"${aname()}_p"
+        putSimpleSequenceInDB(xSeqName, namespace, components.toVector)
+
+        // create an action sequence s
+        val sSeqName = s"${aname()}_s"
+        val sSeq = makeSimpleSequence(sSeqName, namespace, Vector(xSeqName, xSeqName), false) // x is installed in the db already
+        val content = WhiskActionPut(Some(sSeq.exec))
+
+        implicit val stream = new java.io.ByteArrayOutputStream
+        this.outputStream = new PrintStream(stream)
+        try {
+            stream.reset()
+            Console.withOut(stream) {
+                Put(s"$collectionPath/$sSeqName", content) ~> sealRoute(routes(creds)) ~> check {
+                    status should be(OK)
+                    logContains(s"atomic action count ${2 * actionCnt}")
+                }
+            }
+        } finally {
+            stream.close()
+            this.outputStream.close()
+        }
+    }
+
+    /**
+     * Tests the following sequence:
+     * y -> a
+     * x -> b, z
+     * s -> a, x, y
+     *
+     * Update z -> s should not work
+     * Update s -> a, s, b should not work
+     * Update z -> y should work (no cycle) act cnt 1
+     * Update s -> a, x, y, a, b should work (no cycle) act cnt 6
+     */
+    it should "create a complex sequence, allow updates with no cycle and reject updates with cycle" in {
+        val limit = whiskConfig.actionSequenceLimit.toInt
+        assert(whiskConfig.actionSequenceLimit.toInt >= 6)
+        implicit val tid = transid()
+        val actionCnt = 4
+        val aAct = s"${aname()}_a"
+        val yAct = s"${aname()}_y"
+        val yComp = Vector(aAct)
+        // make seq y and store it in the db
+        putSimpleSequenceInDB(yAct, namespace, yComp)
+        val bAct = s"${aname()}_b"
+        val zAct = s"${aname()}_z"
+        val xAct = s"${aname()}_x"
+        val xComp = Vector(bAct, zAct)
+        // make sequence x and install it in db
+        putSimpleSequenceInDB(xAct, namespace, xComp)
+        val sAct = s"${aname()}_s"
+        val sSeq = makeSimpleSequence(sAct, namespace, Vector(s"$aAct", s"$xAct", s"$yAct"), false) // a, x, y  in the db already
+        // create an action sequence s
+        val content = WhiskActionPut(Some(sSeq.exec))
+
+        implicit val stream = new java.io.ByteArrayOutputStream
+        this.outputStream = new PrintStream(stream)
+        try {
+            stream.reset()
+            Console.withOut(stream) {
+                Put(s"$collectionPath/$sAct", content) ~> sealRoute(routes(creds)) ~> check {
+                    status should be(OK)
+                }
+                logContains("atomic action count 4")
+            }
+
+            // update action z to point to s --- should be rejected
+            val zUpdate = makeSimpleSequence(zAct, namespace, Vector(s"$sAct"), false) // s in the db already
+            val zUpdateContent = WhiskActionPut(Some(zUpdate.exec))
+            Put(s"$collectionPath/$zAct?overwrite=true", zUpdateContent) ~> sealRoute(routes(creds)) ~> check {
+                status should be(BadRequest)
+                response.entity.toString should include(sequenceIsCyclic)
+            }
+
+            // update action s to point to a, s, b --- should be rejected
+            val sUpdate = makeSimpleSequence(sAct, namespace, Vector(s"$aAct", s"$sAct", s"$bAct"), false) // s in the db already
+            val sUpdateContent = WhiskActionPut(Some(sUpdate.exec))
+            Put(s"$collectionPath/$sAct?overwrite=true", sUpdateContent) ~> sealRoute(routes(creds)) ~> check {
+                status should be(BadRequest)
+                response.entity.toString should include(sequenceIsCyclic)
+            }
+
+            // update action z to point to y
+            val zSeq = makeSimpleSequence(zAct, namespace, Vector(s"$yAct"), false) // y  in the db already
+            val updateContent = WhiskActionPut(Some(zSeq.exec))
+            stream.reset()
+            Console.withOut(stream) {
+                Put(s"$collectionPath/$zAct?overwrite=true", updateContent) ~> sealRoute(routes(creds)) ~> check {
+                    status should be(OK)
+                }
+                logContains("atomic action count 1")
+            }
+            // update sequence s to s -> a, x, y, a, b
+            val newS = makeSimpleSequence(sAct, namespace, Vector(s"$aAct", s"$xAct", s"$yAct", s"$aAct", s"$bAct"), false) // a, x, y, b  in the db already
+            val newSContent = WhiskActionPut(Some(newS.exec))
+            stream.reset()
+            Console.withOut(stream) {
+                Put(s"${collectionPath}/$sAct?overwrite=true", newSContent) ~> sealRoute(routes(creds)) ~> check {
+                    status should be(OK)
+                }
+                logContains("atomic action count 6")
+            }
+        } finally {
+            stream.close()
+            this.outputStream.close()
+        }
+    }
+
+    /**
+     * Makes a simple sequence action and installs it in the db (no call to wsk api/cli).
+     * All actions are in the default package.
+     *
+     * @param sequenceName the name of the sequence
+     * @param ns the namespace to be used when creating the component actions and the sequence action
+     * @param components the names of the actions (entity names, no namespace)
+     */
+    private def putSimpleSequenceInDB(sequenceName: String, ns: EntityPath, components: Vector[String])(
+        implicit tid: TransactionId) = {
+        val seqAction = makeSimpleSequence(sequenceName, ns, components)
+        put(entityStore, seqAction)
+    }
+
+    /**
+     * Returns a WhiskAction that can be used to create/update a sequence.
+     * If instructed to do so, installs the component actions in the db.
+     * All actions are in the default package.
+     *
+     * @param sequenceName the name of the sequence
+     * @param ns the namespace to be used when creating the component actions and the sequence action
+     * @param componentNames the names of the actions (entity names, no namespace)
+     * @param installDB if true, installs the component actions in the db (default true)
+     */
+    private def makeSimpleSequence(sequenceName: String, ns: EntityPath, componentNames: Vector[String], installDB: Boolean = true)(
+        implicit tid: TransactionId): WhiskAction = {
+        if (installDB) {
+            // create bogus wsk actions
+            val wskActions = componentNames.toSet[String] map { c => WhiskAction(ns, EntityName(c), Exec.js("??")) }
+            // add them to the db
+            wskActions.foreach { put(entityStore, _) }
+        }
+        // add namespace to component names
+        val components = componentNames map { c => stringToFullyQualifiedName(s"/$ns/$c") }
+        // create wsk action for the sequence
+        WhiskAction(namespace, EntityName(sequenceName), Exec.sequence(components))
+    }
+
+    private def logContains(w: String)(implicit stream: java.io.ByteArrayOutputStream): Boolean = {
+        whisk.utils.retry({
+            val log = stream.toString()
+            val result = log.contains(w)
+            assert(result) // throws exception required to retry
+            result
+        }, 10, Some(100 milliseconds))
+    }
+}
diff --git a/tests/src/whisk/core/controller/test/migration/SequenceActionApiMigrationTests.scala b/tests/src/whisk/core/controller/test/migration/SequenceActionApiMigrationTests.scala
new file mode 100644
index 0000000..def4ea8
--- /dev/null
+++ b/tests/src/whisk/core/controller/test/migration/SequenceActionApiMigrationTests.scala
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2015-2016 IBM Corporation
+ *
+ * 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 whisk.core.controller.test.migration
+
+import scala.Vector
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.TestHelpers
+import common.WskTestHelpers
+import spray.http.StatusCodes.OK
+import spray.httpx.SprayJsonSupport.sprayJsonMarshaller
+import spray.httpx.SprayJsonSupport.sprayJsonUnmarshaller
+import spray.json.DefaultJsonProtocol.RootJsObjectFormat
+import spray.json.DefaultJsonProtocol.listFormat
+import spray.json.DefaultJsonProtocol.StringJsonFormat
+import spray.json.DefaultJsonProtocol.vectorFormat
+import spray.json.JsObject
+import spray.json.pimpAny
+import spray.json.pimpString
+
+import whisk.core.controller.WhiskActionsApi
+import whisk.core.controller.test.ControllerTestCommon
+import whisk.core.entity.AuthKey
+import whisk.core.entity.EntityName
+import whisk.core.entity.EntityPath
+import whisk.core.entity.Exec
+import whisk.core.entity.Parameters
+import whisk.core.entity.Subject
+import whisk.core.entity.WhiskAction
+import whisk.core.entity.WhiskActionPut
+import whisk.core.entity.WhiskAuth
+
+/**
+ * Tests migration of a new implementation of sequences: old style sequences can be updated and retrieved - standalone tests
+ */
+@RunWith(classOf[JUnitRunner])
+class SequenceActionApiMigrationTests extends ControllerTestCommon
+    with WhiskActionsApi
+    with TestHelpers
+    with WskTestHelpers {
+
+    behavior of "Sequence Action API Migration"
+
+    val creds = WhiskAuth(Subject(), AuthKey()).toIdentity
+    val namespace = EntityPath(creds.subject())
+    val collectionPath = s"/${EntityPath.DEFAULT}/${collection.path}"
+    def aname = MakeName.next("seq_migration_tests")
+
+    private def seqParameters(seq: Vector[String]) = Parameters("_actions", seq.toJson)
+
+    it should "list old-style sequence action with explicit namespace" in {
+        implicit val tid = transid()
+        val sequence = Vector("/_/a", "/_/x/b", "/n/a", "/n/x/c").map(stringToFullyQualifiedName(_))
+        val actions = (1 to 2).map { i =>
+            WhiskAction(namespace, aname, Exec.sequence(sequence))
+        }.toList
+        actions foreach { put(entityStore, _) }
+        waitOnView(entityStore, WhiskAction, namespace, 2)
+        Get(s"/$namespace/${collection.path}") ~> sealRoute(routes(creds)) ~> check {
+            status should be(OK)
+            val response = responseAs[List[JsObject]]
+
+            actions.length should be(response.length)
+            actions forall { a => response contains a.summaryAsJson } should be(true)
+        }
+    }
+
+    it should "get old-style sequence action by name in default namespace" in {
+        implicit val tid = transid()
+        val sequence = Vector("/_/a", "/_/x/b", "/n/a", "/n/x/c").map(stringToFullyQualifiedName(_))
+        val action = WhiskAction(namespace, aname, Exec.sequence(sequence))
+        put(entityStore, action)
+        Get(s"$collectionPath/${action.name}") ~> sealRoute(routes(creds)) ~> check {
+            status should be(OK)
+            val response = responseAs[WhiskAction]
+            response should be(action)
+        }
+    }
+
+    // this test is a repeat from ActionsApiTest BUT with old style sequence
+    it should "preserve new parameters when changing old-style sequence action to non sequence" in {
+        implicit val tid = transid()
+        val components = Vector("/_/a", "/_/x/b", "/n/a", "/n/x/c")
+        val sequence = components.map(stringToFullyQualifiedName(_))
+        val action = WhiskAction(namespace, aname, Exec.sequence(sequence), seqParameters(components))
+        val content = WhiskActionPut(Some(Exec.js("")), parameters = Some(Parameters("a", "A")))
+        put(entityStore, action, false)
+
+        // create an action sequence
+        Put(s"$collectionPath/${action.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check {
+            deleteAction(action.docid)
+            status should be(OK)
+            val response = responseAs[WhiskAction]
+            response.exec.kind should be(Exec.NODEJS)
+            response.parameters should be(Parameters("a", "A"))
+        }
+    }
+
+    // this test is a repeat from ActionsApiTest BUT with old style sequence
+    it should "reset parameters when changing old-style sequence action to non sequence" in {
+        implicit val tid = transid()
+        val components = Vector("/_/a", "/_/x/b", "/n/a", "/n/x/c")
+        val sequence = components.map(stringToFullyQualifiedName(_))
+        val action = WhiskAction(namespace, aname, Exec.sequence(sequence), seqParameters(components))
+        val content = WhiskActionPut(Some(Exec.js("")))
+        put(entityStore, action, false)
+
+        // create an action sequence
+        Put(s"$collectionPath/${action.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check {
+            deleteAction(action.docid)
+            status should be(OK)
+            val response = responseAs[WhiskAction]
+            response.exec.kind should be(Exec.NODEJS)
+            response.parameters shouldBe Parameters()
+        }
+    }
+
+    it should "update old-style sequence action with new annotations" in {
+        implicit val tid = transid()
+        val components = Vector("/_/a", "/_/x/b", "/n/a", "/n/x/c")
+        val sequence = components.map(stringToFullyQualifiedName(_))
+        val action = WhiskAction(namespace, aname, Exec.sequence(sequence))
+        val content = """{"annotations":[{"key":"old","value":"new"}]}""".parseJson.asJsObject
+        put(entityStore, action, false)
+
+        // create an action sequence
+        Put(s"$collectionPath/${action.name}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check {
+            deleteAction(action.docid)
+            status should be(OK)
+            val response = responseAs[String]
+            // contains the action
+            components map {c => response should include(c)}
+            // contains the annotations
+            response should include("old")
+            response should include("new")
+        }
+    }
+
+    it should "update an old-style sequence with new sequence" in {
+        implicit val tid = transid()
+        // old sequence
+        val seqName = EntityName(s"${aname}_new")
+        val oldComponents = Vector("/_/a", "/_/x/b", "/n/a", "/n/x/c").map(stringToFullyQualifiedName(_))
+        val oldSequence = WhiskAction(namespace, seqName, Exec.sequence(oldComponents))
+        put(entityStore, oldSequence)
+
+        // new sequence
+        val limit = 5 // count of bogus actions in sequence
+        val bogus = s"${aname}_bogus"
+        val bogusActionName = s"/_/${bogus}"   // test that default namespace gets properly replaced
+        // put the action in the entity store so it exists
+        val bogusAction = WhiskAction(namespace, EntityName(bogus), Exec.js("??"), Parameters("x", "y"))
+        put(entityStore, bogusAction)
+        val sequence = for (i <- 1 to limit) yield stringToFullyQualifiedName(bogusActionName)
+        val seqAction = WhiskAction(namespace, seqName, Exec.sequence(sequence.toVector))
+        val content = WhiskActionPut(Some(seqAction.exec), Some(Parameters()))
+
+        // update an action sequence
+        Put(s"$collectionPath/${seqName}?overwrite=true", content) ~> sealRoute(routes(creds)) ~> check {
+            status should be(OK)
+            val response = responseAs[WhiskAction]
+            response.exec.kind should be(Exec.SEQUENCE)
+            response.limits should be(seqAction.limits)
+            response.publish should be(seqAction.publish)
+            response.version should be(seqAction.version.upPatch)
+        }
+    }
+}
diff --git a/tests/src/whisk/core/dispatcher/test/DispatcherTests.scala b/tests/src/whisk/core/dispatcher/test/DispatcherTests.scala
index 0948d42..ff226d4 100644
--- a/tests/src/whisk/core/dispatcher/test/DispatcherTests.scala
+++ b/tests/src/whisk/core/dispatcher/test/DispatcherTests.scala
@@ -68,7 +68,7 @@
         val content = JsObject("payload" -> JsNumber(count))
         val subject = Subject()
         val authkey = AuthKey()
-        val path = FullyQualifiedEntityName(EntityPath("test"), EntityName(s"count-$count"), SemVer())
+        val path = FullyQualifiedEntityName(EntityPath("test"), EntityName(s"count-$count"), Some(SemVer()))
         val msg = Message(TransactionId.testing, path, DocRevision(), subject, authkey, ActivationId(), EntityPath(subject()), Some(content))
         connector.send(msg)
     }